【问题标题】:How to pass parameters to a procedure in assembly?如何将参数传递给程序集中的过程?
【发布时间】:2017-06-24 14:41:00
【问题描述】:

我有一个 C 程序,它从我的程序集文件中调用两个程序,程序定义如下 extern int myfunc(int a,int b)myfunc2(int c,int d) ,现在在 C 中调用 myfunc 之后,我可以像这样访问程序集中的参数:b 位于[BP+6]a 位于[BP+4],这是小模型。 现在我想打电话给myfunc2(int c,int d),但是我在我的myfunc 中时从我的汇编文件中调用。 如何为myfunc2 设置堆栈并传递参数? 它是否会弄乱myfunc 的当前堆栈,如果是,我该如何处理? 我的程序集文件:

.MODEL SMALL
.STACK 100h
.DATA
.CODE
PUBLIC _myfunc
PUBLIC _myfunc2
_myfunc PROC NEAR
.386
PUSH BP
MOV BP,SP
;here i need to do myfun2(1,2)
POP BP
RET
_myfunc ENDP

_myfunc2 PROC NEAR
.386
PUSH BP
MOV BP,SP
MOV DX,[BP+6];get d
MOV AX,[BP+4];get c
ADD AX,DX;add them up
;the return value will be in AX
POP BP
RET
_myfunc2 ENDP

END

我的 C 文件:

#include <stdio.h> 
#include <stdlib.h> 

extern int myfunc(int a,int b);
extern int myfunc2(int c,int d);
int main()
{
    int res;
    res=myfunc(int a,int b);
}

【问题讨论】:

  • 不要描述你的代码....展示一下。
  • 请展示你目前的尝试。
  • 用 C 语言编写一个虚拟的 myfunc 函数并调用 myfunc2,并查看 C 编译器生成的汇编代码。
  • 我添加了我的代码

标签: c assembly x86-16


【解决方案1】:

您通过将值压入堆栈来设置堆栈。堆栈机制的美妙之处在于,将参数传递给另一个函数不会弄乱当前函数的堆栈,前提是您没有做任何严重错误的事情。

您的问题没有简单的答案,因为很大程度上取决于所使用的 ABI(应用程序二进制接口)、函数的调用约定(是cdecl?),等等

最安全的方法是让 C 编译器生成 C 代码的汇编输出,然后照此执行。但总的来说,它看起来像这样:

push ax          ; pass int c parameter (assuming int is 16-bit)
push dx          ; pass int d parameter (assuming int is 16-bit)
call _myfunc2    ; invoke the function
add sp, 4        ; clean up stack (assuming cdecl calling convention)

以上假设 int 是 16 位的,当我听到您谈到 MODEL SMALL 时,我认为这是合理的。

【讨论】:

  • 你能解释一下你为什么在 sp 中加 4 吗?
  • 没关系,我现在明白这与在通话后为 ax 和 dx 做 POP 一样,谢谢。
  • 是的,没错。
【解决方案2】:

因为确实没有一个简单的答案,所以最好的方法是使用 gnu 调试器或文档,但无论如何你最终都会使用 gdb。一种方法是用 C 编写程序,将它们反汇编,然后自己看看调用约定是什么。您可以使用堆栈,也可以像 64 位通常和 32 位系统调用一样轻松地使用寄存器来传递这些简单的值。

//testc.c
int func2(int c, int d)
{
    return c-d;
}


int func(int a, int b)
{
    a+=2;
    b++;
    func2(a,b); 

}


//cfile.c

#include <stdio.h> 
#include <stdlib.h> 

extern int func(int a,int b);
extern int func2(int c,int d);
int main()
{
    int res;
    int b = 4;
    int c = 3;
    res=func(b, c);
}

编译

$ gcc -m32 -g -c testc.c
unroot@flerb:~/stacko$ gcc -m32 -g cfile.o testc.o -o a.out
unroot@flerb:~/stacko$ ./a.out
unroot@flerb:~/stacko$ echo $?
0

在 gdb 中

$ gdb -q a.out
Reading symbols from a.out...done.
(gdb) set listsize 50
(gdb) list 0
1   #include <stdio.h> 
2   #include <stdlib.h> 
3   
4   extern int func(int a,int b);
5   extern int func2(int c,int d);
6   int main()
7   {
8       int res;
9       int b = 4;
10      int c = 3;
11      res=func(b, c);
12  }


(gdb) break main
Breakpoint 1 at 0x57c: file cfile.c, line 9.
(gdb) run
Starting program: /home/unroot/stacko/a.out 

Breakpoint 1, main () at cfile.c:9
9       int b = 4;

在 main 中断时查看堆栈的内容

(gdb) x/20x $esp
0xffffd290: 0x00000001  0xffffd354  0xffffd35c  0x56555611
0xffffd2a0: 0xffffd2c0  0x00000000  0x00000000  0xf7e12276
0xffffd2b0: 0x00000001  0xf7fad000  0x00000000  0xf7e12276
0xffffd2c0: 0x00000001  0xffffd354  0xffffd35c  0x00000000
0xffffd2d0: 0x00000000  0x00000000  0xf7fad000  0xf7ffdc04

只是为了在参数被推送之前快速浏览一下内容

(gdb) x/20x $esp-0x10
0xffffd280: 0x00000003  0x56557000  0x00000001  0x56555577
0xffffd290: 0x00000001  0xffffd354  0xffffd35c  0x56555611
0xffffd2a0: 0xffffd2c0  0x00000000  0x00000000  0xf7e12276
0xffffd2b0: 0x00000001  0xf7fad000  0x00000000  0xf7e12276
0xffffd2c0: 0x00000001  0xffffd354  0xffffd35c  0x00000000
(gdb) disas
Dump of assembler code for function main:
   0x56555560 <+0>: lea    ecx,[esp+0x4]
   0x56555564 <+4>: and    esp,0xfffffff0
   0x56555567 <+7>: push   DWORD PTR [ecx-0x4]
   0x5655556a <+10>:    push   ebp
   0x5655556b <+11>:    mov    ebp,esp
   0x5655556d <+13>:    push   ebx
   0x5655556e <+14>:    push   ecx
   0x5655556f <+15>:    sub    esp,0x10
   0x56555572 <+18>:    call   0x565555af <__x86.get_pc_thunk.ax>
   0x56555577 <+23>:    add    eax,0x1a89
=> 0x5655557c <+28>:    mov    DWORD PTR [ebp-0xc],0x4
   0x56555583 <+35>:    mov    DWORD PTR [ebp-0x10],0x3
   0x5655558a <+42>:    sub    esp,0x8
   0x5655558d <+45>:    push   DWORD PTR [ebp-0x10]
   0x56555590 <+48>:    push   DWORD PTR [ebp-0xc]
   0x56555593 <+51>:    mov    ebx,eax
   0x56555595 <+53>:    call   0x565555c8 <func>
   0x5655559a <+58>:    add    esp,0x10
   0x5655559d <+61>:    mov    DWORD PTR [ebp-0x14],eax
   0x565555a0 <+64>:    mov    eax,0x0
   0x565555a5 <+69>:    lea    esp,[ebp-0x8]
   0x565555a8 <+72>:    pop    ecx
   0x565555a9 <+73>:    pop    ebx
   0x565555aa <+74>:    pop    ebp
   0x565555ab <+75>:    lea    esp,[ecx-0x4]
   0x565555ae <+78>:    ret    
End of assembler dump.
(gdb) break *0x56555595 
Breakpoint 2 at 0x56555595: file cfile.c, line 11.
(gdb) cont
Continuing.

现在看栈的内容,c=3 被压入,然后 b=4(注意我写这个只是为了方便,压入的值是纯值,与一旦它们在堆栈上就代表它们)

Breakpoint 2, 0x56555595 in main () at cfile.c:11
11      res=func(b, c);
(gdb) x/20x $esp
0xffffd280: 0x00000004  0x00000003  0x00000001  0x56555577
0xffffd290: 0x00000001  0xffffd354  0x00000003  0x00000004
0xffffd2a0: 0xffffd2c0  0x00000000  0x00000000  0xf7e12276
0xffffd2b0: 0x00000001  0xf7fad000  0x00000000  0xf7e12276
0xffffd2c0: 0x00000001  0xffffd354  0xffffd35c  0x00000000
(gdb) 

所以 func 调用的参数以相反的顺序推送,推送 3 然后推送 4,很明显,因为堆栈正在向下增长到更小的地址。当被调用函数访问这些参数时,有时会先弹出 4 个,然后使用 esp 弹出 3 个到单独的寄存器,或者如下反汇编所示,被调用函数可以通过来自 ebp 的指针访问它们。

(gdb) disas
Dump of assembler code for function main:
   0x56555560 <+0>: lea    ecx,[esp+0x4]
   0x56555564 <+4>: and    esp,0xfffffff0
   0x56555567 <+7>: push   DWORD PTR [ecx-0x4]
   0x5655556a <+10>:    push   ebp
   0x5655556b <+11>:    mov    ebp,esp
   0x5655556d <+13>:    push   ebx
   0x5655556e <+14>:    push   ecx
   0x5655556f <+15>:    sub    esp,0x10
   0x56555572 <+18>:    call   0x565555af <__x86.get_pc_thunk.ax>
   0x56555577 <+23>:    add    eax,0x1a89
   0x5655557c <+28>:    mov    DWORD PTR [ebp-0xc],0x4
   0x56555583 <+35>:    mov    DWORD PTR [ebp-0x10],0x3
   0x5655558a <+42>:    sub    esp,0x8
   0x5655558d <+45>:    push   DWORD PTR [ebp-0x10]
   0x56555590 <+48>:    push   DWORD PTR [ebp-0xc]
   0x56555593 <+51>:    mov    ebx,eax
=> 0x56555595 <+53>:    call   0x565555c8 <func>
   0x5655559a <+58>:    add    esp,0x10
   0x5655559d <+61>:    mov    DWORD PTR [ebp-0x14],eax
   0x565555a0 <+64>:    mov    eax,0x0
   0x565555a5 <+69>:    lea    esp,[ebp-0x8]
   0x565555a8 <+72>:    pop    ecx
   0x565555a9 <+73>:    pop    ebx
   0x565555aa <+74>:    pop    ebp
   0x565555ab <+75>:    lea    esp,[ecx-0x4]
   0x565555ae <+78>:    ret    
End of assembler dump.

进入函数

(gdb) stepi
func (a=4, b=3) at testc.c:9
9   {
(gdb) disas
Dump of assembler code for function func:
=> 0x565555c8 <+0>: push   ebp
   0x565555c9 <+1>: mov    ebp,esp
   0x565555cb <+3>: call   0x565555af <__x86.get_pc_thunk.ax>
   0x565555d0 <+8>: add    eax,0x1a30
   0x565555d5 <+13>:    add    DWORD PTR [ebp+0x8],0x2
   0x565555d9 <+17>:    add    DWORD PTR [ebp+0xc],0x1
   0x565555dd <+21>:    push   DWORD PTR [ebp+0xc]
   0x565555e0 <+24>:    push   DWORD PTR [ebp+0x8]
   0x565555e3 <+27>:    call   0x565555b3 <func2>
   0x565555e8 <+32>:    add    esp,0x8
   0x565555eb <+35>:    nop
   0x565555ec <+36>:    leave  
   0x565555ed <+37>:    ret    
End of assembler dump.

C 调用函数将返回值压入堆栈,没有任何明显暗示它会这样做,但它在那里占用空间

(gdb) x/20x $esp
0xffffd27c: 0x5655559a  0x00000004  0x00000003  0x00000001
0xffffd28c: 0x56555577  0x00000001  0xffffd354  0x00000003
0xffffd29c: 0x00000004  0xffffd2c0  0x00000000  0x00000000
0xffffd2ac: 0xf7e12276  0x00000001  0xf7fad000  0x00000000
0xffffd2bc: 0xf7e12276  0x00000001  0xffffd354  0xffffd35c

然后我们进入 func 的实质,其中从 ebp 的偏移用于将 2 加到 a(第二个压入堆栈的函数,因此更接近堆栈上的 ebp)并将 1 加到 b它被推到它之前

(gdb) step
10      a+=2;

(gdb) print/x $ebp
$1 = 0xffffd278

(gdb) x/20x $ebp
0xffffd278: 0xffffd2a8  0x5655559a  0x00000004  0x00000003
0xffffd288: 0x00000001  0x56555577  0x00000001  0xffffd354
0xffffd298: 0x00000003  0x00000004  0xffffd2c0  0x00000000
0xffffd2a8: 0x00000000  0xf7e12276  0x00000001  0xf7fad000
0xffffd2b8: 0x00000000  0xf7e12276  0x00000001  0xffffd354


(gdb) disas
Dump of assembler code for function func:
   0x565555c8 <+0>: push   ebp
   0x565555c9 <+1>: mov    ebp,esp
   0x565555cb <+3>: call   0x565555af <__x86.get_pc_thunk.ax>
   0x565555d0 <+8>: add    eax,0x1a30
=> 0x565555d5 <+13>:    add    DWORD PTR [ebp+0x8],0x2      //a+=2
   0x565555d9 <+17>:    add    DWORD PTR [ebp+0xc],0x1      //b++
   0x565555dd <+21>:    push   DWORD PTR [ebp+0xc]      //push b
   0x565555e0 <+24>:    push   DWORD PTR [ebp+0x8]      //push a
   0x565555e3 <+27>:    call   0x565555b3 <func2>
   0x565555e8 <+32>:    add    esp,0x8
   0x565555eb <+35>:    nop
   0x565555ec <+36>:    leave  
   0x565555ed <+37>:    ret    
End of assembler dump.

中断调用并再次检查堆栈

(gdb) break *0x565555e3
Breakpoint 3 at 0x565555e3: file testc.c, line 12.
(gdb) cont
Continuing.

Breakpoint 3, 0x565555e3 in func (a=6, b=4) at testc.c:12
12      func2(a,b); 
(gdb) disas
Dump of assembler code for function func:
   0x565555c8 <+0>: push   ebp
   0x565555c9 <+1>: mov    ebp,esp
   0x565555cb <+3>: call   0x565555af <__x86.get_pc_thunk.ax>
   0x565555d0 <+8>: add    eax,0x1a30
   0x565555d5 <+13>:    add    DWORD PTR [ebp+0x8],0x2
   0x565555d9 <+17>:    add    DWORD PTR [ebp+0xc],0x1
   0x565555dd <+21>:    push   DWORD PTR [ebp+0xc]
   0x565555e0 <+24>:    push   DWORD PTR [ebp+0x8]
=> 0x565555e3 <+27>:    call   0x565555b3 <func2>
   0x565555e8 <+32>:    add    esp,0x8
   0x565555eb <+35>:    nop
   0x565555ec <+36>:    leave  
   0x565555ed <+37>:    ret   

(gdb) x/20x $esp
0xffffd270: 0x00000006  0x00000004  0xffffd2a8  0x5655559a
0xffffd280: 0x00000006  0x00000004  0x00000001  0x56555577
0xffffd290: 0x00000001  0xffffd354  0x00000003  0x00000004
0xffffd2a0: 0xffffd2c0  0x00000000  0x00000000  0xf7e12276
0xffffd2b0: 0x00000001  0xf7fad000  0x00000000  0xf7e12276

Again our variables have been pushed on the stack, again in reverse order, as always, but you could control this too in assembly if you feel like being difficult.

(gdb) stepi
func2 (c=6, d=4) at testc.c:3
3   {
(gdb) disas
Dump of assembler code for function func2:
=> 0x565555b3 <+0>: push   ebp
   0x565555b4 <+1>: mov    ebp,esp
   0x565555b6 <+3>: call   0x565555af <__x86.get_pc_thunk.ax>
   0x565555bb <+8>: add    eax,0x1a45
   0x565555c0 <+13>:    mov    eax,DWORD PTR [ebp+0x8]
   0x565555c3 <+16>:    sub    eax,DWORD PTR [ebp+0xc]
   0x565555c6 <+19>:    pop    ebp
   0x565555c7 <+20>:    ret    
End of assembler dump.

Again the call pushes the return address onto the stack before transfering control to the called function. 
(gdb) x/20x $esp
0xffffd26c: 0x565555e8  0x00000006  0x00000004  0xffffd2a8
0xffffd27c: 0x5655559a  0x00000006  0x00000004  0x00000001
0xffffd28c: 0x56555577  0x00000001  0xffffd354  0x00000003
0xffffd29c: 0x00000004  0xffffd2c0  0x00000000  0x00000000
0xffffd2ac: 0xf7e12276  0x00000001  0xf7fad000  0x00000000

在被调用函数的实质上继续和中断。

(gdb) break *0x565555c0
Breakpoint 4 at 0x565555c0: file testc.c, line 4.
(gdb) cont
Continuing.


Breakpoint 4, func2 (c=6, d=4) at testc.c:4
4       return c-d;


(gdb) disas
Dump of assembler code for function func2:
   0x565555b3 <+0>: push   ebp
   0x565555b4 <+1>: mov    ebp,esp
   0x565555b6 <+3>: call   0x565555af <__x86.get_pc_thunk.ax>
   0x565555bb <+8>: add    eax,0x1a45
=> 0x565555c0 <+13>:    mov    eax,DWORD PTR [ebp+0x8]
   0x565555c3 <+16>:    sub    eax,DWORD PTR [ebp+0xc]
   0x565555c6 <+19>:    pop    ebp
   0x565555c7 <+20>:    ret    
End of assembler dump.

在 func2 的实质检查堆栈,访问变量并发生减法:

(gdb) x/20x $esp
0xffffd268: 0xffffd278  0x565555e8  0x00000006  0x00000004
0xffffd278: 0xffffd2a8  0x5655559a  0x00000006  0x00000004
0xffffd288: 0x00000001  0x56555577  0x00000001  0xffffd354
0xffffd298: 0x00000003  0x00000004  0xffffd2c0  0x00000000
0xffffd2a8: 0x00000000  0xf7e12276  0x00000001  0xf7fad000
(gdb) x/20x $ebp
0xffffd268: 0xffffd278  0x565555e8  0x00000006  0x00000004
0xffffd278: 0xffffd2a8  0x5655559a  0x00000006  0x00000004
0xffffd288: 0x00000001  0x56555577  0x00000001  0xffffd354
0xffffd298: 0x00000003  0x00000004  0xffffd2c0  0x00000000
0xffffd2a8: 0x00000000  0xf7e12276  0x00000001  0xf7fad000

所以这里 [ebp+0x8] = 6 和 [ebp+0xc] = 4 并用 eax 寄存器中的减法指令修改值,将结果返回到 eax 寄存器中。

C 的默认约定是让调用者在将控制权转移给被调用者之前推送返回地址,并让被调用者调整堆栈和基指针,但是当你调用自己的函数时,你可以做任何你想做的事情并返回您自己的功能。在这里,我使用 C 程序来说明 C 的作用,但是如果您使用 C 调用一个汇编程序,该程序又调用另一个汇编程序,那么您可以显式控制这些汇编程序之间的基指针(如果需要)。您可以选择手动调整它们并跳转到您的第二个函数调用,这将绕过推送返回地址和设置堆栈的自动调用过程,并不完全有用;或者汇编调用指令将使用相同的过程来初始化被调用的函数,并且您可以期望偏移量与 C 函数中的相同。

Good link on x86 Call conventions including cdecl and stdcallA handy syscall reference showing use of registers for function calls

这是一个使用寄存器传递 32 位变量的简单示例,不需要堆栈。 //testasm32.asm 部分 .text 全局_start

_start:
    mov ecx, hello1
    call print_string
    mov ecx, hello2
    call print_string

    mov eax, 1
    int 0x80

print_string:
    mov eax, 4
    mov ebx, 1
    mov edx, 6
    int 0x80
    ret

section .data
    hello1 db "Hello1"
    hello2 db "Hello2"

$ nasm -f elf32 testasm32.asm
unroot@flerb:~/LearningLinuxBinaryAnalysis_Code$ ld -m elf_i386 -o testasm32 testasm32.o
unroot@flerb:~/LearningLinuxBinaryAnalysis_Code$ ./testasm32
Hello1Hello2

$ echo $?
1

有趣的是,ebx 保存了退出系统调用的错误代码,因此它返回 1,因为 ebx 在 print_string 命令中被设置为 1 并且未被清除。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-09-08
    • 2016-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多