基于linux-汇编深入理解c程序

一个简单的入门实例

实现一个简单的exit(0);
exit.s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  # 数据段的开始
.section .data
# 代码段开始(存放程序指令的部分)
.section .text
#注意这里的.globl 表示汇编程序不应在汇编之后废弃次符号(_start) ,因为连接
# 器需要它。_start 是个特殊的符号,总是用.global 来标记,因为它标记了该程序的开始位置。
.globl _start
#定义_start 标签(符号)的值,该标签后面的:告诉汇编程序,以该符号的只作为下一条指令或者下一个数据元素的位置
_start:
# 用于退出linux 内核命令号(系统调用)
movl $1,%eax
# 将0 作为退出码,echo $? 可以看到该值
movl $0,%ebx
#调用系统0x80号中断(唤醒内核运行退出命令)
int $0x80
```
编译运行方法:
```sh
as exit.s -o exit.o
ld exit.o -o exit
```
## 寄存器
通用寄存器:
```asm
%eax
%ebx
%ecx
%edx
%edi
%esi

专用寄存器:

1
2
3
4
5
6
7
8
9
  %ebp(栈基址)
%esp(栈顶)
%eip(指令寄存器)
%eflags
```
## 寻址方式
内存地址引用的通用格式如下:
```asm
地址或者偏移(%基址寄存器,%索引寄存器,比例因子)

所有字段都是可选的,要计算地址可以按照下面公式进行计算:
结果地址 = 地址或者偏移 + %基址或者偏移量寄存器 + 比例因子 * %索引寄存器
其中地址或者偏移量 以及比例因子都必须是常量,其余的两个必须是寄存器。如果要省略任何一项,那么等式可以将0代替该项。

  1. 直接寻址方式
    此模式可以通过使用地址或者偏移部分实现:(即上面公式中%基址或者偏移量寄存器 和 %索引寄存器都是0)

    1
    movl ADDRESS, %eax

    以上指定将内存地址ADDRESS 加载到%eax

  2. 索引寻址方式
    这种模式将通过使用·地址者偏移量以及%索引寄存器部分实现(%索引寄存器可以是任何通用寄存器)。

    1
    2
    3
    4
    5
    6
    7
    8
        movl string_start(,%ecx,1), %eax  
    ```
    该指令从string_start 处开始,将改地址与1*%ecx 相加,并将所得值加载到 %eax

    0. 间接寻址方式
    间接寻址方式从寄存器从寄存器指定的地址加载值例如:如果%eax 保存着一个地址,我们可以通通过下面的方式将该地址中的值移入到%ebx
    ```asm
    movl (%eax), %ebx
  3. 基址寻址方式
    基址寻址方式与间接寻址方式类似,不同之处在于它将一个常量值与寄存器中的地址相加。例如:
    如果已有一个记录,其中年龄值位于记录起始地址后4个字节处,该记录的起始地址在%eax中,那么
    下面的指令将年龄提取到%ebx中:

    1
    movl 4(%eax), %ebx
  4. 立即寻址方式
    用于直接将值加载到寄存器或者存储位置.数字前面加$表示是立即数,否则表示的是将位于存储位置12中的值而不是12 加载到%eax

    1
    movl $12, %eax
  5. 寄存器寻址方式
    寄存器寻址方式仅仅是将数据移入或者移出寄存器

注意:除了立即寻址方式外的其他寻址方式都可用作源或者目的操作数。而立即寻址方式只能是源操作数
不同的指令操作的数据字节数不同例如movl 移动一个字,movb 移动一个字节大小

下面是%eax寄存器的布局:

1
2
3
|<--------%eax-------->|
[ ][ ][ %ah][%al ]
|<--%ax--->|

  1. 实例二
    查找最大值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    .section .data
    data_items: #数据项存放在数据段
    .long

    .section .text

    .globl _start
    _start:
    movl $0,%edi $0移入索引寄存器
    movl data_items(,%edi,4), %eax#加载第一个元素到%eax
    movl %eax ,%ebx #%ebx 存放最大值,由于是第一项所以就是最大值

    start_loop:
    cmpl $0,$eax #数据中0 作为数据结束的标志
    je loop_exit
    incl %edi #索引指向下一个元素
    movl data_items(,%edi,4) ,%eax
    cmp %ebx,%eax #比较当前元素于原有的最大值
    jle start_loop

    movl %eax,%ebx #将新的最大值移入%ebx
    jmp start_loop

    loop_exit:
    movl $1 ,%eax #1 是exit()系统调用
    int $0x80

    汇编连接:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
        as maximum.s -o maximum.o
    ld maximum.o -o maximum
    ./maximum
    $echo $? #注意这里会打印最大值,因为我们程序计算结果就是保存在%ebx中
    ```

    ***
    ## 关于函数
    一个函数一般由一下几个部分:`函数名、函数参数、局部变量、静态变量、全局变量、返回地址、返回值`
    约定函数参数从又到左入栈。

    ```asm
    参数 #N <----- N*4 + 4(%ebp) [高地址]
    ...
    参数 2 <----- 12(%ebp)
    参数 1 <----- 8(%ebp)
    返回地址 <----- 4(%ebp)
    旧%ebp <----- (%esp) 和 (%ebp) [低地址]
    局部变量 1 <---- -4(%ebp)
    局部变量 2 <---- -8(%ebp) 和 (%esp)

    从上面可以看出 通过%ebp寄存器可以获取到参数、局部变量
    如果需要从函数返回,那么需要执行一下指令

    1
    2
    3
    movl %ebp,%esp
    popl %ebp
    ret
  • 函数示例
    计算2^3 + 5^2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
     #主程序中的所有内容都存储在寄存器中,
    #因此数据段不含任何内容
    .section .data
    .section .text
    .globl _start

    _start:
    pushl $3
    pushl $2
    call power
    addl $8, %esp
    pushl %eax

    pushl $2
    pushl $5
    call power
    addl $8, %esp

    popl %ebx

    addl %eax, %ebx

    movl $1, %eax
    int $0x80
    .type power,@function #告诉连接器 factorial 是个函数,处罚法我们需要在别的程序中使用factorial函数,否则这条指令不是必须的。
    power:
    pushl %ebp
    movl %esp,%ebp
    subl $4,%esp
    movl 8(%ebp),%ebx
    movl 12(%ebp),%ecx

    movl %ebx, -4(%ebp)
    power_loop_start:
    cmpl $1,%ecx
    je end_power
    movl -4(%ebp),%eax
    imull %ebx,%eax
    movl %eax, -4(%ebp)

    decl %ecx
    jmp power_loop_start

    end_power:
    movl -4(%ebp),%eax
    movl %ebp,%esp
    popl %ebp
    ret
  • 递归调用函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    .section .data
    #本程序无全局数据
    .section .text

    .globl _start
    .globl factorial #除非我们希望与其他程序共享该函数,否则无需此项
    _start:
    pushl $4
    call factorial
    addl $4,%esp
    movl %eax,%ebx

    movl $1, %eax
    int $0x80

    # 这是实际函数的含义
    .type factorial,@function
    factorial:
    pushl %ebp
    movl %esp,%ebp
    movl 8(%ebp),%eax
    cmpl $1, %eax
    je end_factorial
    decl %eax #否则就递减
    pushl %eax
    call factorial
    movl 8(%ebp),%ebx

    imull %ebx,%eax

    end_factorial:
    movl %ebp, %esp
    popl %ebp
    ret
坚持原创技术分享,您的支持奖鼓励我继续创作!