将命令行参数正确放置到堆栈上

时间:2012-10-31 22:40:27

标签: assembly command-line x86 stack

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

我正在尝试实现基于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 print读取到屏幕上。

.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

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

1 个答案:

答案 0 :(得分: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希望函数参数放在堆栈上的返回地址之上。这意味着第一个参数应该是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可以在那里存储返回地址。您似乎没有在运行该程序的代码中使用ebpesiedi,因此只需将其添加到列表中即可使用其中的任何内容在内联汇编中销毁的寄存器,然后更改您的start以存储第一个返回地址。

pop    %esi  # Pop first return address
call   main
push   %esi  # Restore first return address
lretw