MuFen 发表于 2019-3-15 13:05

汇编小白---解决一些常用汇编指令问题---简单通俗易懂

本帖最后由 MuFen 于 2019-4-30 15:18 编辑

下面将用一个简单的例子来讲解,保证百分百通俗易懂;


例子比较简单,对于小白,每一个错误都是一笔财富,在没有讲解之前,你大致看一下就会发现你还是非常地富裕...


不知道如何搞出源程序的汇编代码的童鞋可以先去看我的上一篇帖子:[https://www.52pojie.cn/thread-896166-1-1.html]

想下载本篇源码继续研究的也可以下载:---附件下载---


统一解释:H表示该数为16进制数,不知道的先把这句话背诵几万遍...,例如:88H就表示一个16进制数88,FDH就表示一个16进制数FD,如果还想知道FD是啥鬼东西的人,我建议你还是别看下面了,先学几天计算机再来看,因为我发现你除了会拆电脑,啥都不会...,还想来学汇编?当然我以后还是会补充这一部分内容的,因为对只会拆电脑的你,我脚的你也不知道我到底在说啥,其实说了一大堆,我也不知道我在说啥...(这就very尴尬辽)

main:   file format elf32-avr


Disassembly of section .text:

00000000 <__vectors>:
   0:      0c c0               rjmp      .+24             ; 0x1a <__ctors_end>
   2:      1f c0               rjmp      .+62             ; 0x42 <__bad_interrupt>
   4:      1e c0               rjmp      .+60             ; 0x42 <__bad_interrupt>
   6:      1d c0               rjmp      .+58             ; 0x42 <__bad_interrupt>
   8:      1c c0               rjmp      .+56             ; 0x42 <__bad_interrupt>
   a:      1b c0               rjmp      .+54             ; 0x42 <__bad_interrupt>
   c:      1a c0               rjmp      .+52             ; 0x42 <__bad_interrupt>
   e:      19 c0               rjmp      .+50             ; 0x42 <__bad_interrupt>
10:      18 c0               rjmp      .+48             ; 0x42 <__bad_interrupt>
12:      17 c0               rjmp      .+46             ; 0x42 <__bad_interrupt>
14:      16 c0               rjmp      .+44             ; 0x42 <__bad_interrupt>
16:      15 c0               rjmp      .+42             ; 0x42 <__bad_interrupt>
18:      14 c0               rjmp      .+40             ; 0x42 <__bad_interrupt>

0000001a <__ctors_end>:
1a:      11 24               eor      r1, r1
1c:      1f be               out      0x3f, r1      ; 63
1e:      cf e5               ldi      r28, 0x5F      ; 95
20:      d2 e0               ldi      r29, 0x02      ; 2
22:      de bf               out      0x3e, r29      ; 62
24:      cd bf               out      0x3d, r28      ; 61

00000026 <__do_copy_data>:
26:      10 e0               ldi      r17, 0x00      ; 0
28:      a0 e6               ldi      r26, 0x60      ; 96
2a:      b0 e0               ldi      r27, 0x00      ; 0
2c:      ea e9               ldi      r30, 0x9A      ; 154
2e:      f0 e0               ldi      r31, 0x00      ; 0
30:      03 c0               rjmp      .+6            ; 0x38 <.do_copy_data_start>

00000032 <.do_copy_data_loop>:
32:      c8 95               lpm
34:      31 96               adiw      r30, 0x01      ; 1
36:      0d 92               st      X+, r0

00000038 <.do_copy_data_start>:
38:      aa 36               cpi      r26, 0x6A      ; 106
3a:      b1 07               cpc      r27, r17
3c:      d1 f7               brne      .-12             ; 0x32 <.do_copy_data_loop>
3e:      0f d0               rcall      .+30             ; 0x5e <main>
40:      2a c0               rjmp      .+84             ; 0x96 <_exit>

00000042 <__bad_interrupt>:
42:      de cf               rjmp      .-68             ; 0x0 <__vectors>

//从这里开始,往下讲解,有兴趣的可以仔细研究一下在这之前的东西

00000044 <funA>:
int a = 268, b = 78;
char s = {26, 66};
int k = {280, 36};

要想了解其值如何变化,我们还是要先把这些变量赋值语句来搞定,我们用一张表简单的弄清楚这些东西:



地址16进制变量备注
0x00600ca的低位a=12
0x006101a的高位a=256
0x00624eb的低位b=78
0x006300b的高位b=0
0x00641ass=26
0x006542ss=66
0x006618k低位k=24
0x006701k高位k=256
0x006824k低位k=36
0x006900k高位k=0


看不懂上面这张表的童鞋可以留言,我会补充讲解;

int funA(int x, int y)
{
44:      68 0f               add      r22, r24
//ADD是普通的加法指令,双字数据长度。
46:      79 1f               adc      r23, r25
//ADC是带进位的加法指令,把值分割为多个双字数据元素,并且对每个元素执行独立的加法操作。
return x+y;
}

//MOV指令是数据传送指令,为方便理解,下面给出一些例子
//MOV AX,2000H;将16位数据2000H传送到AX寄存器
//MOV AL,20H;将8位数据20H传送到AL寄存器
//MOV AX,BX;将BX寄存器的16位数据传送到AX寄存器
//MOV AL,;将2000H单元的内容传送到AL寄存器

48:      86 2f               mov      r24, r22
4a:      97 2f               mov      r25, r23
4c:      08 95               ret

//RET指令是子程序的最后一条指令,即恢复断点,返回主程序。 没有要求RET指令非要和哪一条指令要配对使用。
//RET是子程序返回指令,放在子程序的结尾,当子程序执行完后,靠该指令返回主程序。
//单独重点讲解一下过程:
//44:      68 0f add      r22, r24 ;这句r22和r24是寄存器,add是汇编指令,68 0f 是汇编后的机器码,其中r22是存储x低位的寄存器,r24是存储y低位的寄存器;这句话的意义是:r22和r24相加,结果存储在r22中
//46:      79 1f adc      r23, r25 ;这句r23和r25是寄存器,adc是汇编指令,79 1f 是汇编后的机器码,其中r23是存储x高位的寄存器,r25是存储y高位的寄存器;;这句话的意义是:r23和r25相加,结果存储在r23中
//上面两句是为了实现:X+Y
//下面是为了实现return
//48:      86 2f mov      r24, r22 ;这句r22和r24是寄存器,mov是汇编指令,86 2f 是汇编后的机器码;这句话的意义是:将r22寄存器的16位数据传送到r24寄存器
//4a:      97 2f mov      r25, r23 ;这句r23和r25是寄存器,mov是汇编指令,97 2f 是汇编后的机器码;这句话的意义是:将r23寄存器的16位数据传送到r25寄存器
//4c:      08 95 ret ;这句的解释稍微往上翻一下,你就看到了
//这里有人有疑问了,为什么这里的寄存器用的是r22-r25而不用别的,this why在main函数里面对funA(x,y)传递参数时,用的是r22-r25,main函数中调用funA()函数的语句是:d = funA(b, d); ,你往后翻,然后看d = funA(b, d); 下面的汇编语句,你就会发现b所用的寄存器就是这里x用的寄存器,d和y亦如此。
//还有人会问,为什么return的是r24和r25而不是r22和r23,因为这是AVR的约定,好似你不能把你老爸叫妈妈,不能把你老妈叫爸爸的约定(也可以说是规定)
//还有人会说,既然ADD AX,BX执行的是将BX寄存器的16位数据传送到AX寄存器,我们在return的时候却返回了BX,为什么不直接用ADD BX,AX就可以省去MOVBX,AX的过程,如果你能想到这个问题,那么你一定是个智商距离250差不远的童鞋了,其实按照你这样做也是对的,但是AVR编译器却不是这么做的,那么当你以后自己写编译器的时候你大可这样优化你的编译过程。


0000004e <funB>:

int funB(int x, int y)
{
if(x > y)
4e:      68 17               cp      r22, r24
//cp:比较,无进位
50:      79 07               cpc      r23, r25
//cpc:考虑进位
52:      14 f0               brlt      .+4            ; 0x58 <funB+0xa>
//brlt:若if条件为假则顺序执行,为真则跳转,根据解释0x58 <funB+0xa> ,这里我们可以看到,若跳转则跳转到了0x58位置
54:      60 e0               ldi      r22, 0x00      ; 0
56:      71 e0               ldi      r23, 0x01      ; 1

//ldi 代码意义:装入一个8位立即数到指定寄存器16~31
//下面结合另一段汇编加注释
//ldi r31,-0x62 ;将16进制数62H的补码装入寄存器r31
//这里-0x62是一个带符号数,在汇编语言中是这个数的补码。
//正数的补码就是它自身,负数补码就是将原码取反后+1。
//0x62=0b01100010(原码)
//-0x62=0b10011110=0x9e(补码)
//综上所述执行这一段代码后,寄存器r31被装入0x9e。

return y;
else
return 256;
}
58:      86 2f               mov      r24, r22
5a:      97 2f               mov      r25, r23
5c:      08 95               ret

0000005e <main>:

int main(void)
{
int d = 326;

k = 20;
5e:      84 e1               ldi      r24, 0x14      ; 20
//上面这句的意思是将16进制数14H的补码装入寄存器r24
//正数的补码就是它自身,寄存器r24被装入0x14
60:      90 e0               ldi      r25, 0x00      ; 0
62:      90 93 69 00         sts      0x0069, r25
66:      80 93 68 00         sts      0x0068, r24
//sts:直接存储到数据空间
a = k;
6a:      80 91 66 00         lds      r24, 0x0066
6e:      90 91 67 00         lds      r25, 0x0067
72:      90 93 61 00         sts      0x0061, r25
76:      80 93 60 00         sts      0x0060, r24

//这里重点讲一下传参(传递参数)的神奇过程:
//a = k;的意思是把k的值赋值给a,
//6a:      80 91 66 00 lds      r24, 0x0066,这句中0x0066是k的低位地址,r24是一个寄存器,lds是汇编指令,80 91 66 00 是汇编后的机器码;这句的意义是:将0x0066装入寄存器r24,也可以理解为将k的低位地址装入寄存器r24(虽然并不能这么理解,但是暂时你就勉强这么理解吧!)
//6e:      90 91 67 00 lds      r25, 0x0067,这句中0x0067是k的高位地址,r25是一个寄存器,lds是汇编指令,90 91 67 00 是汇编后的机器码;这句的意义是:将0x0067装入寄存器r25,也可以理解为将k的高位地址装入寄存器r25(虽然并不能这么理解,但是暂时你就勉强这么理解吧!)
//72:      90 93 61 00 sts      0x0061, r25,这句中0x0061是a的高位地址,r25是一个寄存器,sts是汇编指令,90 63 61 00 是汇编后的机器码;这句话的意义是:将r25直接存储到0x0061这个地址
//76:      80 93 60 00 sts      0x0060, r24,这句中0x0060是a的低位地址,r24是一个寄存器,sts是汇编指令,80 93 60 00 是汇编后的机器码;这句话的意义是:将r24直接存储到0x0060这个地址
//大致的过程就是酱紫:k的地址传给某个特定的寄存器,这个寄存器再存储到a的地址,过程就是这么个过程,道理就是这么个道理...

d = funA(b, d);
7a:      80 91 62 00         lds      r24, 0x0062//低位
7e:      90 91 63 00         lds      r25, 0x0063//高位
//lds:这条指令的功能是把后面指向的地址,高位存放在DS中,低位存放在reg中.
82:      66 e4               ldi      r22, 0x46      ; 70
84:      71 e0               ldi      r23, 0x01      ; 1
//这里我们会看到一个问题:有些汇编指令后面是8位2进制操作数例如 ldi r23, 0x01中的0x01,而有些是16位2进制操作数,如 lds   r25, 0x0063中的0x0063,首先这里我们要先明白, 7a:80 91 62 00   lds    r24, 0x0062 和7e:   90 91 63 00   lds   r25, 0x0063是对参数b的操作,而82:      66 e4               ldi      r22, 0x46和 84:      71 e0               ldi      r23, 0x01   是对d进行操作,仔细观察,我们会发现b是全局变量,d是局部变量。
86:      de df               rcall      .-68             ; 0x44 <funA>

//RCALL指令属于ROM空间的相对寻址范畴,CALL属于ROM空间的直接寻址范畴
//相对寻址,操作数是地址的偏移量;这时还要加上一个固定地址,才能得到绝对地址
//绝对寻址,操作数是绝对地址。
//上述rcall便是如下操作:.-68为偏移量,这里的68是十进制数,68的十六进制表示为44H,这里的固定地址为88H,为什么为88H呢?我们看一下rcall前面的机器码de为86H,机器码df为87H,那么当df解析完之后,下一位就是88H了。

88:      28 2f               mov      r18, r24
8a:      39 2f               mov      r19, r25
8c:      2d 5f               subi      r18, 0xFD      ; 253
//subi:subi rd k;操作:rd<— rd-k,
8e:      3f 4f               sbci      r19, 0xFF      ; 255
//sbci:sbci rd k;操作:rd<— rd-k-c,c为进位
d += 3;

//上面的sub的原始解释为减法,而我们想要的是d=d+3,明明是加法,为什么要用减法汇编指令来执行呢?我可以很明确的告诉你:这鸟玩意儿我也不知道...,但是我们或许可以了解一下它是怎么把加法操作变成减法汇编的,仔细观察,你会发现,3跟253有这某种内在的联系,253+3刚好等于256,256为16的二次方,那么它怎么来的我们就能理解了,他把+3变成了-3的补码即-256(如果需要我再讲解一下原码、补码、反码等尼玛,可以留言,下一篇会补充)
//有人会问 中的FD是啥玩意儿,你猜一下这是啥玩意,熟悉10进制和16进制之间转换的人,都知道FDH是10进制的253,我就不告诉你这是啥玩意儿了。

return d;
}
90:      82 2f               mov      r24, r18
92:      93 2f               mov      r25, r19
94:      08 95               ret

00000096 <_exit>:
96:      f8 94               cli

00000098 <__stop_program>:
98:      ff cf               rjmp      .-2            ; 0x98 <__stop_program>



选择在这里发帖的最重要的一个原因是:这里大佬多,小学生和喷子老哥比较少;

bachelor66 发表于 2019-3-15 13:39

虽然看不懂,但跟着尝试慢慢学。                           

MuFen 发表于 2019-3-15 13:33

Reynard 发表于 2019-3-15 13:20
这方面不是很懂 帮顶一下 让大佬来

认真看一下,我觉得我还是说的很容易理解的

lichao521 发表于 2019-3-15 13:15

看不懂 这是干啥的啊?

御花猫 发表于 2019-3-15 13:16

感谢分享,慢慢学习。

davidmao 发表于 2019-3-15 13:19

跟着学习。。。。

Reynard 发表于 2019-3-15 13:20

这方面不是很懂 帮顶一下 让大佬来

Reynard 发表于 2019-3-15 13:37

MuFen 发表于 2019-3-15 13:33
认真看一下,我觉得我还是说的很容易理解的

主要是我太菜了,不是大佬的关系……

明星家电维修部 发表于 2019-3-22 20:18

学习资料

seedhk 发表于 2019-8-29 14:26

感觉硬生生的搞复杂了。。。
页: [1] 2
查看完整版本: 汇编小白---解决一些常用汇编指令问题---简单通俗易懂