NASM基本输入输出程序崩溃

时间:2013-11-25 08:42:18

标签: linux assembly nasm rhel

在这个帖子How do i read single character input from keyboard using nasm (assembly) under ubuntu?之后,我正在尝试编译一个回显NASM输入的程序。 我已经制作了以下文件:

my_load2.asm:

%include "testio.inc"
global _start
section .text
_start: mov eax, 0
call canonical_off
call canonical_on

testio.inc:

termios:        times 36 db 0
stdin:          equ 0
ICANON:         equ 1<<1
ECHO:           equ 1<<3

canonical_off:
        call read_stdin_termios

        ; clear canonical bit in local mode flags
        push rax
        mov eax, ICANON
        not eax
        and [termios+12], eax
        pop rax

        call write_stdin_termios
        ret

echo_off:
        call read_stdin_termios

        ; clear echo bit in local mode flags
        push rax
        mov eax, ECHO
        not eax
        and [termios+12], eax
        pop rax

        call write_stdin_termios
        ret

canonical_on:
        call read_stdin_termios

        ; set canonical bit in local mode flags
        or dword [termios+12], ICANON

        call write_stdin_termios
        ret

echo_on:
        call read_stdin_termios

        ; set echo bit in local mode flags
        or dword [termios+12], ECHO

        call write_stdin_termios
        ret

read_stdin_termios:
        push rax
        push rbx
        push rcx
        push rdx

        mov eax, 36h
        mov ebx, stdin
        mov ecx, 5401h
        mov edx, termios
        int 80h

        pop rdx
        pop rcx
        pop rbx
        pop rax
        ret

write_stdin_termios:
        push rax
        push rbx
        push rcx
        push rdx

        mov eax, 36h
        mov ebx, stdin
        mov ecx, 5402h
        mov edx, termios
        int 80h

        pop rdx
        pop rcx
        pop rbx
        pop rax
        ret

然后我跑:

[root@localhost asm]# nasm -f elf64 my_load2.asm 
[root@localhost asm]# ld -m elfx86_64 my_load2.o -o my_load2

当我尝试运行它时,我得到:

[root@localhost asm]# ./my_load2
Segmentation fault

调试器说:

(gdb) run
Starting program: /root/asm/my_load2

Program received signal SIGSEGV, Segmentation fault.
0x00000000004000b1 in canonical_off ()

有人可以解释为什么没有“导入”步骤会崩溃吗? 另外,我在Win7 64位的Virtualbox中运行RHEL。这会导致编译问题吗?

1 个答案:

答案 0 :(得分:2)

首先,让我们解决丹尼尔提到的不退出的问题。让我们注释掉两个call指令,这样程序基本上什么都不做:

%include "testio.inc"
global _start
section .text
_start: mov eax, 0
;call canonical_off
;call canonical_on

当我们运行时:

$ ./my_load2 
Segmentation fault (core dumped)

它还在死!丹尼尔是对的 - 你需要退出:

%include "testio.inc"
global _start
section .text
_start: mov eax, 0
;call canonical_off
;call canonical_on

mov eax, 1
mov ebx, 0
int 0x80

这一次:

$ ./my_load2
$ 

没有段错误。所以让我们取消注释call s:

%include "testio.inc"
global _start
section .text
_start: mov eax, 0
call canonical_off
call canonical_on

mov eax, 1
mov ebx, 0
int 0x80

再次运行:

$ ./my_load2
Segmentation fault (core dumped)

我们再次遇到段错误。但至少我们可以(相当)确定它来自call ed例程中的一个。

使用strace运行可执行文件也非常有用:

$ strace ./my_load2
execve("./my_load2", ["./my_load2"], [/* 57 vars */]) = 0
setsockopt(0, SOL_IP, 0x400080 /* IP_??? */, NULL, 0) = -1 EFAULT (Bad address)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x40008c} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)

setsockopt行归因于ioctl中发生的read_stdin_termios请求。 strace告诉我们返回值为EFAULTsetsockopt(2)手册页告诉我们这种情况发生在:

  

optval指向的地址不在进程地址空间的有效部分。

实际上,这告诉我们写入termios结构的内存块是只读的。弗兰克是对的;程序中的所有内容 - 包括termios空间和所有代码 - 都在只读.text部分。您可以通过以下方式看到:

$ objdump -h my_load2.o

my_load2.o:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         000000cd  0000000000000000  0000000000000000  000001c0  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE

即。只有一个部分.text,而且是READONLY

然而,实际导致段错误的那一行是这样的:

and [termios+12], eax

因为它也试图写入(只读)termios内存。

解决此问题的最快方法是将termios内存放在.data部分以及.text部分中的所有其他内容中:

section .data

termios:        times 36 db 0

section .text

stdin:          equ 0
ICANON:         equ 1<<1
ECHO:           equ 1<<3

canonical_off:
        call read_stdin_termios

[...]

stdinICANONECHO可以位于只读.text部分,因为它们仅用作常量 - 即我们不会写入那些内存。)

进行了这些更改后:

$ ./my_load2 
$ 

程序正常运行并退出。