Linux 32bit 程序的函数调用

今天研究了一下Linux 32bit程序的函数调用过程,主要是从汇编指令角度去分析函数调用前后栈帧的变化。

首先解释一下什么是栈帧。

我们知道进程在执行时的地址空间包含3部分:代码段、全局数据段、堆以及栈。其中堆和栈都是动态变化的内存区,并且堆是向上生长,栈是向下生长。

那么栈有什么作用呢?一是保存函数(过程)的本地变量:如果函数的local变量都是基本类型并且数量较少,那么可以直接保存在通用寄存器里。但是总有些特殊的情况:1)变量较多,寄存器数量不够;2)存在结构型的变量;3)如果对local变量有取址的操作(&)。那么就需要把他们放在栈里。注意:对于动态分配的内存(malloc/new),数据是存放在堆区域的,但是指向数据区域的指针是放在栈里的。

此外,函数一般是需要caller传递参数过来的,既可以通过寄存器传递,也可以通过栈传递。

这样,其实栈是服务于函数 (过程)的,每一个函数所占用的栈区域就是一个栈帧。一个栈帧的范围由两个寄存器来指定:%ebp(frame pointer)和%esp(stack pointer)。%ebp在函数执行过程中是不变的;%esp是动态变化的。因此,在传递函数参数时,需要通过%ebp来定位参数的地址。

下面分析一下函数调用的过程,主要关注以下方面:

  • 参数如何传递
  • 结果如何返回
  • 栈帧如何转移
  • 栈帧如何恢复

测试分析的代码如下:

// code.c
int accum = 0;

int sum(int x, int y)
{
    int t = x + y;
    accum += t;
    return t;
}

int solve()
{
    int x = 3;
    int y = 4;
    return sum(x, y);
}

solve()函数是caller,sum()则是callee。看一下汇编之后的代码:

$ gcc -m32 -O1 -S code.c

因为是在64bit Linux上测试,因此加上-m32选项,表示编译32bit的程序。

// code.s
    .file   "code.c"
    .text
.globl sum
    .type   sum, @function
sum:
    pushl   %ebp
    movl    %esp, %ebp
    movl    12(%ebp), %eax
    addl    8(%ebp), %eax
    addl    %eax, accum
    popl    %ebp
    ret
    .size   sum, .-sum
.globl solve
    .type   solve, @function
solve:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $8, %esp
    movl    $4, 4(%esp)
    movl    $3, (%esp)
    call    sum
    leave
    ret
    .size   solve, .-solve
.globl accum
    .bss
    .align 4
    .type   accum, @object
    .size   accum, 4
accum:
    .zero   4
    .ident  "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
    .section    .note.GNU-stack,"",@progbits

上面的代码很简单,可以用来观察在进入sum()后栈帧的情况,如下图所示:

callee返回之前的栈帧

总的来说:

  1. caller负责在自己的栈帧中保存自己的ebp寄存器,并将传递参数之前的esp寄存器值放在ebp寄存器中;
  2. caller同时需要将函数参数入栈;
  3. callee需要将第1点中所述的caller保存在ebp中的esp值入栈,并从栈帧中取参数。
  4. caller执行call指令转移到callee,其中涉及到eip的保存;
  5. callee执行ret指令返回到caller,其中涉及到eip的恢复。但在这之前需要先将ebp寄存器的值pop出来;
  6. caller通过leave指令将保存在ebp中的值重新恢复到esp寄存器,从而恢复执行调用之前的现场。

需要提到的是,上面的代码实例中通过eax寄存器保存函数返回值。

Linux下,可以使用SocketCAN来调用CAN接口。SocketCAN是Linux内核中实现的CAN协议栈,它将CAN总线抽象为一个网络接口,可以使用常规的网络套接字API来进行访问和控制CAN总线。 以下是一个简单的使用SocketCAN的例子: 1. 首先,需要确保已经安装了 SocketCAN 的驱动程序。常见的驱动程序包括 can-utils、linux-can 和 can4linux 等。 2. 通过命令行工具 ip 命令来配置 CAN 接口。例如,要配置 CAN0 接口,可以运行以下命令: ``` sudo ip link set can0 up type can bitrate 500000 ``` 其中,can0 是 CAN 接口的名称,500000 是 CAN 总线的比特率。 3. 在应用程序中,可以使用常规的网络套接字API来访问 CAN 总线。例如,可以使用 socket() 函数创建一个套接字,然后使用 bind() 函数将套接字绑定到 CAN 接口,最后使用 sendto() 和 recvfrom() 函数发送和接收 CAN 数据帧。 以下是一个简单的示例代码: ```c #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <net/if.h> #include <linux/can.h> #include <linux/can/raw.h> int main(void) { int s; struct sockaddr_can addr; struct can_frame frame; struct ifreq ifr; // 创建 CAN 套接字 s = socket(PF_CAN, SOCK_RAW, CAN_RAW); if (s < 0) { perror("socket"); return -1; } // 绑定到 CAN0 接口 strcpy(ifr.ifr_name, "can0"); ioctl(s, SIOCGIFINDEX, &ifr); addr.can_family = AF_CAN; addr.can_ifindex = ifr.ifr_ifindex; if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { perror("bind"); return -1; } // 发送 CAN 数据帧 memset(&frame, 0, sizeof(frame)); frame.can_id = 0x123; frame.can_dlc = 8; frame.data[0] = 0x11; frame.data[1] = 0x22; frame.data[2] = 0x33; frame.data[3] = 0x44; frame.data[4] = 0x55; frame.data[5] = 0x66; frame.data[6] = 0x77; frame.data[7] = 0x88; if (write(s, &frame, sizeof(frame)) != sizeof(frame)) { perror("write"); return -1; } // 接收 CAN 数据帧 if (read(s, &frame, sizeof(frame)) != sizeof(frame)) { perror("read"); return -1; } printf("ID=0x%X DLC=%d data=%02X %02X %02X %02X %02X %02X %02X %02X\n", frame.can_id, frame.can_dlc, frame.data[0], frame.data[1], frame.data[2], frame.data[3], frame.data[4], frame.data[5], frame.data[6], frame.data[7]); // 关闭套接字 close(s); return 0; } ``` 该代码使用 SocketCAN API 发送一个 CAN 数据帧,并等待接收一个 CAN 数据帧。需要注意的是,CAN 数据帧的 ID、数据长度和数据内容都需要根据实际情况进行修改。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值