2012-10-31 17 views
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 

我已經看過堆棧如何爲函數調用做準備,以爲它可能是一個類似的過程,但我仍處於斷開狀態。 有什麼想法?

+0

進度:我想我可能已經找到了一個粗略的工作。我完全從主聲明中刪除了argc和argv。有兩個原因,1。我只會填充我自己的變量,在我的寵物操作系統中可能還有其他一些缺失的東西,gcc在編譯這些測試程序時會假定其工作。 現在我手動從內聯程序集中取出了不太難的值。儘管如此,仍然希望能夠正常工作。 addl $ 4,%esp \t#stack on stack; movl(%esp),%eax \t#轉到下一個 – tman

回答

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可以在那裏存儲返回地址。看起來您並沒有在運行程序的代碼中使用ebp,esiedi,因此您可以通過簡單地將它們添加到內聯程序集中已銷燬的寄存器列表中來使用它們,然後更改您的start在那裏存儲第一個返回地址。

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