Assembly 打印字符串的第一个字符会导致分段错误
我正在尝试使用Assembly 打印字符串的第一个字符会导致分段错误,assembly,x86,segmentation-fault,Assembly,X86,Segmentation Fault,我正在尝试使用printf在64位Ubuntu 20环境中,将字符串stru 1的第一个字符打印到x86汇编中的标准输出,以下是我的尝试: ; nasm -f test.asm && gcc -m32 -o test test.asm.o section .text global main extern printf some_proc: mov esi, str_1 mov eax, [esi] push eax push argv_str
printf
在64位Ubuntu 20
环境中,将字符串stru 1
的第一个字符打印到x86汇编
中的标准输出,以下是我的尝试:
; nasm -f test.asm && gcc -m32 -o test test.asm.o
section .text
global main
extern printf
some_proc:
mov esi, str_1
mov eax, [esi]
push eax
push argv_str
call printf
pop eax
ret
main:
call some_proc
ret
section .data
str_1 db `three`
argv_str db `%c\n`
这将产生:
t
Segmentation fault (core dumped)
预期标准输出:
t
为什么此代码会导致分段错误?如何修改代码以输出预期的标准输出?您有几个错误:
- 将两个4字节参数推送到
printf
的堆栈上。在SysV调用约定中,printf
会将它们保留在那里,因此您有责任在之后调整堆栈以“删除”它们。请记住,ret
将在堆栈顶部查找返回地址;当您的代码保持不变时,将出现您推送的eax
中的字符值。这不是一个有效的地址,因此尝试返回该地址会导致segfault。您可以通过两次pingpop
来删除这些参数,或者通过简单地将8
添加到esp
,从而将堆栈指针移回原来的位置来更有效地删除这些参数
- 当前版本的i386 SysV ABI要求在调用任何函数之前将堆栈对齐到16字节。考虑到
call
本身将堆栈上的4个字节作为返回地址,以及每个push
指令,您可以计算出调用某些进程和printf
所需的必要调整,并根据需要从esp
中添加或减去。(从技术上讲,您可以避免在调用some_proc
之前对齐堆栈,而只需在printf
之前修复堆栈,但这太容易出错。)某些32位库的编译方式可能不强制执行此要求,但64位代码肯定需要它,因此遵守此要求是一个好习惯
esi
是被调用方根据(记住这些!)保存的寄存器。如果要修改它,必须保存以前的内容并在返回之前还原它们(例如,在函数顶部按esi,在最后按esi)。或者选择调用者保存的寄存器,例如ecx
。但是,如下文所述,您根本不需要为str1
的地址使用寄存器
mov-eax,[esi]
是32位加载,因为eax
是32位寄存器。因此,这将从位置stru 1
加载eax
的4个字节,这将导致它包含值0x65726874
(字节th re
作为一个小的endian整数)。这实际上可能不会导致问题,因为printf
应该将其int
参数转换回unsigned char
以进行打印,因此您应该只获取低字节0x74='t'
,但它仍然很奇怪,如果字符串很短并且与未映射页面相邻,它可能会中断
更安全的是mov al,[esi]
,它只将一个字节加载到al
,这是eax
的低字节,但高3字节中的任何垃圾都将留在那里。您可以使用xor eax,eax
预先将eax
归零,但也可以使用movzx
指令一箭双雕,该指令将较小的操作数归零为较大的操作数:movzx eax,byte[esi]
当然,首先将地址放入esi
是多余的,因为地址可以指定为立即数:mov al、[str_1]
或movzx eax,byte[str_1]
。这样就无需保存/恢复esi
main
应返回退出代码,并且返回值总是进入eax
。您的eax
将包含您的字符,或者可能包含来自printf
的返回值,具体取决于推送/弹出的最终位置。其中任何一个都将是一个奇怪的非零退出代码,您的shell将认为程序遇到了错误。因此,在从main
返回之前,将eax
归零,以表示成功
argv_str
是一个与argv
无关的字符串的奇怪名称
我将修改您的程序如下:
; nasm -f test.asm && gcc -m32 -o test test.asm.o
section .text
global main
extern printf
some_proc:
sub esp, 4 ; 8 more bytes pushed before call to printf
movzx eax, byte [str_1]
push eax
push argv_str
call printf
add esp, 12
ret
main:
sub esp, 12
call some_proc
xor eax, eax
add esp, 12
ret
section .data
str_1 db `three`
argv_str db `%c\n`
您是否尝试过使用调试器查看故障实际发生的位置?通过将入口点设置为\u start
,您绕过了所有标准库的初始化代码,因此您不能期望任何标准库函数(如printf
)正常工作。这只适用于完全不需要C库的程序,并且将通过原始系统调用完成所有工作。如果你需要C库,那么你需要让你的程序的入口点是main
。在mov eax中,你有另一个bug,[esi]
在你只需要1的时候加载4个字节。哦,但实际上可能导致你崩溃的bug是你把printf的参数推到堆栈上,你有责任把它们放回去,但是你没有。我怎么只加载一个字节?在将代码的入口点更改为main
并使用gcc
编译之后,我仍然收到一个分段错误。在调用函数/过程之前,如何确切地知道堆栈的增量/减量是多少?@dnsis_445:基本上,您考虑如何使其成为16的倍数,基于对堆栈指针所做的其他更改。请参阅本网站上有关堆栈对齐的其他问题。