x86 Linux ELF 加载器问题
x86 Linux ELF Loader Troubles
我正在尝试为 x86-64 Linux 编写一个 ELF 可执行加载程序,类似于 , which was implemented on ARM. Chris Rossbach's advanced OS class 包括一个基本上可以完成我想做的事情的实验室。我的目标是将一个简单的(静态链接的)"hello world" 类型的二进制文件加载到我的进程的内存中,并且 运行 它没有 execve
ing。我已经成功 mmap
了 ELF 文件,设置了堆栈,并跳转到了 ELF 的入口点 (_start
)。
// put ELF file into memory. This is just one line of a complex
// for() loop that loads the binary from a file.
mmap((void*)program_header.p_vaddr, program_header.p_memsz, map, MAP_PRIVATE|MAP_FIXED, elffd, program_header.p_offset);
newstack = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); // Map a page for the stack
if((long)newstack < 0) {
fprintf(stderr, "ERROR: mmap returned error when allocating stack, %s\n", strerror(errno));
exit(1);
}
topstack = (unsigned long*)((unsigned char*)newstack+4096); // Top of new stack
*((unsigned long*)topstack-1) = 0; // Set up the stack
*((unsigned long*)topstack-2) = 0; // with argc, argv[], etc.
*((unsigned long*)topstack-3) = 0;
*((unsigned long*)topstack-4) = argv[1];
*((unsigned long*)topstack-5) = 1;
asm("mov %0,%%rsp\n" // Install new stack pointer
"xor %%rax, %%rax\n" // Zero registers
"xor %%rbx, %%rbx\n"
"xor %%rcx, %%rcx\n"
"xor %%rdx, %%rdx\n"
"xor %%rsi, %%rsi\n"
"xor %%rdi, %%rdi\n"
"xor %%r8, %%r8\n"
"xor %%r9, %%r9\n"
"xor %%r10, %%r10\n"
"xor %%r11, %%r11\n"
"xor %%r12, %%r12\n"
"xor %%r13, %%r13\n"
"xor %%r14, %%r14\n"
:
: "r"(topstack-5)
:"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11", "r12", "r13", "r14");
asm("push %%rax\n"
"pop %%rax\n"
:
:
: "rax");
asm("mov %0,%%rax\n" // Jump to the entry point of the loaded ELF file
"jmp *%%rax\n"
:
: "r"(jump_target)
: );
然后我在 gdb
中单步执行此代码。我在下面粘贴了启动代码的前几条指令。一切正常,直到第一个 push
指令(加星号)。 push
导致段错误。
0x60026000 xor %ebp,%ebp
0x60026002 mov %rdx,%r9
0x60026005 pop %rsi
0x60026006 mov %rsp,%rdx
0x60026009 and [=12=]xfffffffffffffff0,%rsp
0x6002600d * push %rax
0x6002600e push %rsp
0x6002600f mov [=12=]x605f4990,%r8
我试过:
- 使用原始进程中的堆栈。
mmap
创建一个新堆栈(如上面的代码):(1) 和 (2) 都会导致段错误。
push
ing 和 pop
ing to/from 堆栈,然后 jmp
ing 到加载的 ELF 文件。这不会导致段错误。
- 将第二个
mmap
中堆栈的保护标志更改为 PROT_READ | PROT_WRITE | PROT_EXEC
。这没有什么区别。
我怀疑这可能与段描述符有关(也许?)。似乎我正在加载的 ELF 文件中的代码没有对堆栈段的写入权限,无论它位于何处。我没有尝试修改新加载的二进制文件的段描述符或更改架构段寄存器。这是必要的吗?有人知道如何解决这个问题吗?
事实证明,当我单步执行 gdb
中加载的代码时,当我键入 nexti
时,调试器始终会忽略第一个 push
指令,而是继续执行.事实上,导致段错误的不是 push
指令,而是 C 库起始代码中的一条晚得多的指令。问题是在我没有进行错误检查的初始二进制加载中对 mmap
的调用失败引起的。
关于gdb
随机决定继续执行而不是步进:这可以通过在跳转到新加载的可执行文件后从目标可执行文件加载符号来解决。
我正在尝试为 x86-64 Linux 编写一个 ELF 可执行加载程序,类似于 execve
ing。我已经成功 mmap
了 ELF 文件,设置了堆栈,并跳转到了 ELF 的入口点 (_start
)。
// put ELF file into memory. This is just one line of a complex
// for() loop that loads the binary from a file.
mmap((void*)program_header.p_vaddr, program_header.p_memsz, map, MAP_PRIVATE|MAP_FIXED, elffd, program_header.p_offset);
newstack = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0); // Map a page for the stack
if((long)newstack < 0) {
fprintf(stderr, "ERROR: mmap returned error when allocating stack, %s\n", strerror(errno));
exit(1);
}
topstack = (unsigned long*)((unsigned char*)newstack+4096); // Top of new stack
*((unsigned long*)topstack-1) = 0; // Set up the stack
*((unsigned long*)topstack-2) = 0; // with argc, argv[], etc.
*((unsigned long*)topstack-3) = 0;
*((unsigned long*)topstack-4) = argv[1];
*((unsigned long*)topstack-5) = 1;
asm("mov %0,%%rsp\n" // Install new stack pointer
"xor %%rax, %%rax\n" // Zero registers
"xor %%rbx, %%rbx\n"
"xor %%rcx, %%rcx\n"
"xor %%rdx, %%rdx\n"
"xor %%rsi, %%rsi\n"
"xor %%rdi, %%rdi\n"
"xor %%r8, %%r8\n"
"xor %%r9, %%r9\n"
"xor %%r10, %%r10\n"
"xor %%r11, %%r11\n"
"xor %%r12, %%r12\n"
"xor %%r13, %%r13\n"
"xor %%r14, %%r14\n"
:
: "r"(topstack-5)
:"rax", "rbx", "rcx", "rdx", "rsi", "rdi", "r8", "r9", "r10", "r11", "r12", "r13", "r14");
asm("push %%rax\n"
"pop %%rax\n"
:
:
: "rax");
asm("mov %0,%%rax\n" // Jump to the entry point of the loaded ELF file
"jmp *%%rax\n"
:
: "r"(jump_target)
: );
然后我在 gdb
中单步执行此代码。我在下面粘贴了启动代码的前几条指令。一切正常,直到第一个 push
指令(加星号)。 push
导致段错误。
0x60026000 xor %ebp,%ebp
0x60026002 mov %rdx,%r9
0x60026005 pop %rsi
0x60026006 mov %rsp,%rdx
0x60026009 and [=12=]xfffffffffffffff0,%rsp
0x6002600d * push %rax
0x6002600e push %rsp
0x6002600f mov [=12=]x605f4990,%r8
我试过:
- 使用原始进程中的堆栈。
mmap
创建一个新堆栈(如上面的代码):(1) 和 (2) 都会导致段错误。push
ing 和pop
ing to/from 堆栈,然后jmp
ing 到加载的 ELF 文件。这不会导致段错误。- 将第二个
mmap
中堆栈的保护标志更改为PROT_READ | PROT_WRITE | PROT_EXEC
。这没有什么区别。
我怀疑这可能与段描述符有关(也许?)。似乎我正在加载的 ELF 文件中的代码没有对堆栈段的写入权限,无论它位于何处。我没有尝试修改新加载的二进制文件的段描述符或更改架构段寄存器。这是必要的吗?有人知道如何解决这个问题吗?
事实证明,当我单步执行 gdb
中加载的代码时,当我键入 nexti
时,调试器始终会忽略第一个 push
指令,而是继续执行.事实上,导致段错误的不是 push
指令,而是 C 库起始代码中的一条晚得多的指令。问题是在我没有进行错误检查的初始二进制加载中对 mmap
的调用失败引起的。
关于gdb
随机决定继续执行而不是步进:这可以通过在跳转到新加载的可执行文件后从目标可执行文件加载符号来解决。