2

如果我的术语不正确,请原谅我。

我正在尝试实现一个基于 x86 gnu c 的系统,它能够将命令行参数传递给程序。不要与在程序中访问它们混淆,实际上是在将执行传递给用户程序之前设置堆栈。

从我收集到的内容中,argc 和 argv 被推入堆栈,但正是我遗漏了一些东西的构造过程。下面是我执行另一个程序的方式。

__asm__ __volatile__ ("pushl %%ds\n" /* save data and extra segment registers */
        "pushl %%es\n" 
        "movl %%esp, %%ebx\n" 
        "movl %%ebx, oldsp\n" 
        "movl %%ss, %%ebx\n" 
        "movl %%ebx, oldss\n" 
        "movl %0, %%ds\n"   /* set data segment to new user base */

        "movl %0, %%ss\n" 
        "movl $0xfff0, %%ebx\n" /* start of the new user stack pointer */
        "movl %%ebx, %%esp\n"
        "movl %2, %%eax\n"  /* place i into eax - push it onto the stack*/
        "pushl %%eax\n"
        "pushl %%eax\n"
        "lcallw  *%%fs:(%1)\n" 
        "movl %%fs:oldss, %%ebx\n" 
        "movl %%ebx, %%ss\n" 
        "movl %%fs:oldsp, %%ebx\n" 
        "movl %%ebx, %%esp\n" 
        "popl  %%es\n"  /* restore old segment registers */
        "popl  %%ds\n"
        :
        :"a" (userbase), "d" (&useg), "r" (i)
        :"%ebx", "eax", "memory");  /* prevents gcc from optimizing useg away*/

我的印象是在堆栈指针更新后我可以将值压入堆栈。我显然没有得到我推入堆栈的值,所以我什至不确定我是否以正确的方式去做。

下面是一个简单的测试程序,它试图将 argc 打印到屏幕上。

.file    "prog3.c"
#APP
.code16gcc

call main

lretw

.section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string    "\r\nstring: %u"
#NO_APP
.section    .text.startup,"ax",@progbits
.globl    main
.type    main, @function
main:
.LFB0:
.cfi_startproc
pushl    %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl    %esp, %ebp
.cfi_def_cfa_register 5
andl    $-16, %esp
subl    $16, %esp
movl    8(%ebp), %eax
movl    %eax, 4(%esp)
movl    $.LC0, (%esp)
call    printf
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size    main, .-main
.ident    "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section    .note.GNU-stack,"",@progbits

我已经查看了堆栈是如何为函数调用准备的,认为这可能是一个类似的过程,但我仍然处于断开状态。有什么想法吗?

4

1 回答 1

0

让我们看看程序尝试读取其参数时的堆栈,以及一切是如何到达那里的:

mov    %ebx, %esp # 0xfff0
push   %eax       # 0xffec i
push   %eax       # 0xffe8 i
lcallw *%fs:useg  # 0xffe4 return address 1
call   main       # 0xffe0 return address 2
push   %ebp       # 0xffdc old ebp
mov    %esp, %ebp # ebp = 0xffdc

GCC 期望函数参数被放置在堆栈上的返回地址之上。这意味着第一个参数应该是 at ebp + 8。但是,您可以在此处看到,该位置的值是lcallw指令的返回地址,因此主函数将其视为第一个参数。为了获得放置在那里的参数,您需要让调用的用户代码main(让我们称之为start)在两个返回地址之间复制它,或者根本不更改该代码中堆栈的大小。

start由于堆栈上参数的长度当前是恒定的并且很小,因此简单地复制数据并不困难。您只需要重新推送这两个值,start如下所示:

push   8(%esp) # Copy the high value
push   8(%esp) # Copy the low value
call   main
add    $8,%esp
lretw

对于更长或非常量的参数,这会更慢,因为您必须确定要复制多少,并复制所有内容。

如果start在调用main. 由于它需要将自己的返回地址放在堆栈上,这意味着它需要将另一个返回地址移动到其他地方。最好的方法是让您的系统假设通常保留的寄存器之一不是,以便start可以将返回地址存储在那里。看起来你没有在运行程序的代码中使用ebp, esi, 或edi任何东西,所以你可以使用它们中的任何一个,只需将其添加到内联程序集中的已销毁寄存器列表中,然后更改你start的存储第一个退货地址在那里。

pop    %esi  # Pop first return address
call   main
push   %esi  # Restore first return address
lretw
于 2012-11-01T03:46:40.040 回答