我一直在为Ubuntu Linux 12.04、Intel x86_64架构开发一个自定义PE binfmt
处理程序(如果这听起来很熟悉,我已经发布了一些与该项目相关的问题)。如果我提供的信息太多,我会提前道歉。
binfmt
处理程序是相当标准的;我读取PE头和节,然后将这些节写入用户空间内存中节表中指定的地址。然后,当一切就绪时,我打电话给
start_thread(regs, entry_addr, current->mm->start_stack);
就像内置的Linux处理程序一样;在我的情况下,regs = 0xcf7dffb4
、entry_addr = 0x401000
和start_stack = 0xbffff59b
。
我有以下代码,在英特尔x86汇编:
push ebp
mov ebp, esp
mov eax, 4
add eax, 5
pop ebp
ret
我用fasm将这个程序编译成PE格式的可执行文件(math1.exe),并用insmod
安装我的binfmt
处理程序。如果我在gdb
中调试这个程序,我会看到:
(gdb) set disassembly-flavor intel
(gdb) x/6i 0x401000
0x401000: push ebp
0x401001: mov ebp,esp
0x401003: mov eax,0x4
0x401008: add eax,0x5
0x40100b: pop ebp
0x40100c: ret
所以我知道代码加载到了正确的地址。然后:
(gdb) run
Starting program: /media/sf_Sandbox/math1.exe
Program received signal SIGSEGV, Segmentation fault.
0x0040100c in ?? ()
当我进行寄存器转储时:
(gdb) info registers
eax 0x9 9
ecx 0x81394e8 135501032
edx 0x8137808 135493640
ebx 0x8139548 135501128
esp 0xbfffe59b 0xbfffe59b
ebp 0x0 0x0
esi 0x81394e8 135501032
edi 0x2f7ff4 3112948
eip 0x40100c 0x40100c
...other registers...
您可以看到,代码确实是因为eax = 0x9
而执行的,这是应该的。不过,从表面上看,我找不到任何理由让ret
语句出错。检查dmesg
,我发现
math1.exe[1864] general protection ip:40100c sp:bffff5bd error:0
但我发现很少有关于可能导致这种情况的文件。我知道问题不在于代码本身,因为用相同的汇编程序编译成ELF格式的相同代码运行起来没有任何问题。
我目前关于这个问题的理论是:
- 我并没有真正弄乱处理程序中的堆栈指针。内置的Linux处理程序(ELF、a.out和平面格式,仅举三个例子)有一个函数
create_*_tables()
,用于处理argc
、argv
和envp
参数。一开始我没有包含这个函数,因为测试程序不接受任何输入,但实现create_flat_tables()
函数(来自平面处理程序)到目前为止还不能解决问题。(我知道盲目粘贴和调用其他模块的函数是一个糟糕的想法,但该函数的a.out和flat版本本质上是相同的,所以它似乎不太依赖于可执行文件的格式;我想我应该试一试。) - 我发现了这篇关于在执行
main()
之前和之后发生的函数调用链的文章。math1.exe的objdump
只包含上面给出的汇编代码,但同一程序的objdump
在汇编成ELF格式(生成一个*.o
文件)并与gcc
链接(获得一个ELF二进制文件)后,包含了文章中提到的其他函数(_start()
、__libc_start_main()
等)。也许这些函数在Linux平台上比我以前认为的更强制性
我正在寻找我可以采取的任何解释/建议/进一步的故障排除步骤。提前感谢!
您确实需要实现_start
和__libc_start_main
序列的至少一个方面:调用_exit syscall。您不能仅仅从execve创建的框架中执行一个"ret",并期望它会导致进程干净地终止。从main返回以退出进程是C特性,而您的程序不是C。
我的记忆在系统调用接口上有点模糊,但我相信它是这样的:
- 将%eax设置为系统调用编号(
_NR_exit
) - 将%ebx设置为第一个参数(退出代码)
- int$0x80
%ecx、%edx的附加参数。。。我不确定订单。但是_exit()只需要一个arg,我很确定它在%ebx中。