Assembly 将命令行参数正确放置到堆栈上
如果我的术语不正确,请原谅 我正在尝试实现一个基于x86GNUC的系统,它能够将命令行参数传递给程序。不要与在程序中访问它们混淆,实际上是在将执行传递给用户程序之前设置堆栈Assembly 将命令行参数正确放置到堆栈上,assembly,command-line,x86,stack,Assembly,Command Line,X86,Stack,如果我的术语不正确,请原谅 我正在尝试实现一个基于x86GNUC的系统,它能够将命令行参数传递给程序。不要与在程序中访问它们混淆,实际上是在将执行传递给用户程序之前设置堆栈 __asm__ __volatile__ ("pushl %%ds\n" /* save data and extra segment registers */ "pushl %%es\n" "movl %%esp, %%ebx\n" "movl %%ebx, oldsp\
__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和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
我已经了解了堆栈是如何为函数调用做好准备的,认为它可能是一个类似的过程,但我仍然处于脱节状态。
有什么想法吗?让我们看看程序试图读取其参数时的堆栈,以及所有内容是如何到达的:
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
可以将返回地址存储在那里。看起来您没有对运行程序的代码中的任何内容使用ebp
、esi
或edi
,因此您可以使用其中任何一个,只需将其添加到内联程序集中已销毁的寄存器列表中,然后更改start
以在其中存储第一个返回地址即可
pop %esi # Pop first return address
call main
push %esi # Restore first return address
lretw
进步:我想我可能已经找到了一个粗糙的工作。我从主声明中完全删除了argc和argv。原因有二:1。我将暂时填充我自己的变量,第二,在我的宠物操作系统中可能缺少gcc在编译这些测试程序时所假定的其他东西。现在,我从一些内联程序集中手动获取堆栈中的值,这并不难。但我仍然希望它能正常工作。添加$4,%esp#堆栈上的参数;移动(%esp),%eax#移动到下一个