OC进化汇编语言

汇编语言知多少(四): AT&T 汇编语法

2018-10-26  本文已影响553人  Lin__Chuan

在前几篇文章里我们一直聊的是 Intel 格式的 8086汇编, 这篇文章我们聊聊 AT&T 格式的汇编语法.

AT&T VS Intel

  1. 基于 x86 架构 的处理器所使用的汇编指令一般有两种格式.
  1. 基于ARM 架构 的处理器所使用的汇编指令一般有一种格式, 这种处理器常用语嵌入式设备, 移动设备, 以高性能, 低能耗见长

64位 AT&T汇编的寄存器

  1. 有16个常用的64位寄存器
  1. 寄存器的具体用途

64位, 32位, 16位, 8位 寄存器的显示.


栈帧


这两张图虽然高地址的方向是反的, 但他们说的是同一个问题


调试

在解析汇编程序的时候, 有一些 LLDB 指令是很好用的

还有 JCC 的指令表

指令 解释 描述
JE, JZ equal, zero 结果为零则跳转(相等时跳转)
JNE, JNZ not equal, not zero 结果不为零则跳转(不相等时跳转)
JS sign(有符号\有负号) 结果为负则跳转
JNS not sign(无符号\无负号) 结果为非负则跳转
JP, JPE parity even 结果中1的个数为偶数则跳转
JNP, JPO parity odd 结果中1的个数为偶数则跳转
JO overflow 结果溢出了则跳转
JNO not overflow 结果没有溢出则跳转
JB, JNAE below, not above equal 小于则跳转 (无符号数)
JNB, JAE not below, above equal 大于等于则跳转 (无符号数)
JBE, JNA below equal, not above 小于等于则跳转 (无符号数)
JNBE, JA not below equal, above 大于则跳转(无符号数)
JL, JNGE little, not great equal 小于则跳转 (有符号数)
JNL, JGE not little, great equal 大于等于则跳转 (有符号数)
JLE, JNG little equal, not great 小于等于则跳转 (有符号数)
JNLE, JG not little equal, great 大于则跳转(有符号数)

实战1: 计算 (a++) + (a++) + (a++) = ?

这次我们选择创建一个简单的 Swift 项目, 运行在iOS模拟器中. 代码如下, 由于 Swift 已经不支持 a++, ++a 这种操作, 所以我自定义实现了一个.

在 Xcode 的菜单栏中, Debug -> Debug workflow -> 选择 Always Show Disassembly, 这是控制是否显示汇编程序
在项目中设置断点, 程序运行到断点处, 触发中断, Xcode 界面显示当前程序的汇编界面.

接下来我们来解读一下这些汇编指令

0x10d9b6c96 <+118>: movq   $0x1, -0x28(%rbp)
0x10d9b6c9e <+126>: callq  0x10d9b6e10               ; Test_Swift_Assembly.++ postfix(inout Swift.Int) -> Swift.Int at ViewController.swift:24
0x10d9b6c79 <+89>:  movq   0x45f8(%rip), %rsi        ; "viewDidLoad"
0x10d9b6c80 <+96>:  movq   %rdx, -0x50(%rbp)
0x10d9b6c84 <+100>: callq  0x10d9b8354               ; symbol stub for: objc_msgSendSuper2
0x10d9b6c89 <+105>: movq   -0x48(%rbp), %rdi
0x10d9b6c8d <+109>: callq  0x10d9b835a               ; symbol stub for: objc_release

调用完 super.viewDidLoad()

0x10d9b6c92 <+114>: leaq   -0x28(%rbp), %rdi
0x10d9b6c96 <+118>: movq   $0x1, -0x28(%rbp)
<注释>上面可以翻译成 mov $0x1 [rbp-0x28] 将立即数1 赋值到 [rbp-0x28] 所指的内存单元
<注释>这是一个 局部变量, 对应源代码中的 int a = 1.
 
0x10d9b6c9e <+126>: callq  0x10d9b6e10               ; Test_Swift_Assembly.++ postfix(inout Swift.Int) -> Swift.Int at ViewController.swift:24
<注释> 调用 ++ 函数

0x10d9b6ca3 <+131>: leaq   -0x28(%rbp), %rdi
0x10d9b6ca7 <+135>: movq   %rax, -0x58(%rbp)
<注释> 此时 %rax 中的值为 1
0x10d9b6cab <+139>: callq  0x10d9b6e10               ; Test_Swift_Assembly.++ 
<注释> 调用 ++ 函数

postfix(inout Swift.Int) -> Swift.Int at ViewController.swift:24
0x10d9b6cb0 <+144>: movq   -0x58(%rbp), %rdx
0x10d9b6cb4 <+148>: addq   %rax, %rdx
<注释> %ax 中的值(2)  + %rdx 中的值(1) 存储在 %rdx 寄存器中(3)

0x10d9b6cb7 <+151>: seto   %r8b
0x10d9b6cbb <+155>: movq   %rdx, -0x60(%rbp)
<注释>将%rdx中的值赋值给 -0x60(%rbp)

0x10d9b6cbf <+159>: movb   %r8b, -0x61(%rbp)
0x10d9b6cc3 <+163>: jo     0x10d9b6daf               ; <+399> at ViewController.swift:17
0x10d9b6cc9 <+169>: leaq   -0x28(%rbp), %rdi
0x10d9b6ccd <+173>: callq  0x10d9b6e10               ; Test_Swift_Assembly.++ postfix(inout Swift.Int) -> Swift.Int at ViewController.swift:24
0x10d9b6cd2 <+178>: movq   -0x60(%rbp), %rdi
<注释> %rax 的值为3,  %rdi 的值为3

0x10d9b6cd6 <+182>: addq   %rax, %rdi
<注释>3 + 3 = %rdi 的值为 6

0x10d9b6cd9 <+185>: seto   %cl
0x10d9b6cdc <+188>: movq   %rdi, -0x70(%rbp)
<注释> 将 %rdi 的值赋给 -0x70(%rbp),  值为6

0x10d9b6ce0 <+192>: movb   %cl, -0x71(%rbp)
0x10d9b6ce3 <+195>: jo     0x10d9b6db1               ; <+401> at ViewController.swift:17
0x10d9b6ce9 <+201>: movq   -0x70(%rbp), %rax
<注释>将 -0x70(%rbp) 的值赋给 %rax,  值为6

<注释>接下来是传递参数打印 c 的值
->  0x10d9b6ced <+205>: movl   $0x1, %ecx

下面是一个挑战

var a = 2
let c = a++ + a++ + a++  // 2 + 3 + 4 = 9 , a = 5
let c2 = ++a + a++ + a++ // 6 + 6 + 7 = 19, a = 8
let c3 = ++a + ++a + a++ // 9 + 10 + 10 = 29, a = 11
print(c3, a)  // 29, 11

实战2: 解读 zombieObject

在 MRC 环境下, 我们运行下面这段代码.

NSArray *arr = @[@"a", @"b", @"c"];
NSLog(@"1==>%ld", arr.retainCount);   // 1
    
[arr release];   // 0
NSLog(@"1==>%ld", arr.retainCount);  // 报错
    
[arr release];
NSLog(@"1==>%ld", arr.retainCount);

程序肯定会报错, EXC_BAD_Address, 这类访问内存错误的问题, 原因大部分是 向一个已释放的对象发送消息

如果你对汇编比较熟悉的话, 直接观察这个汇编代码, 也可以定位问题位置.


但是, 如果你看不懂会汇编, 一时找不到错误, Xcode 已经内置了工具帮助我们调试.

在 Edit Scheme —> Diagnostics —> Memory Management —> Zombie Objects


打开 Zombie Objects 后,重新运行代码, 我们会发现

开启前
开启后

本着大胆猜想, 小心求证的原则, 接下来我们验证一下.

验证猜想

验证第一步

没什么不是看源码不能解决的 :] 如果能找到 Runtime 的源码就好了.

Apple 是有提供 Runtime 的源码大致实现. 在这里可以下载到, 它是一个 OC 项目, 下载后打开就可以了.


在搜索框了搜索 zombie, 大致找到了相关信息, 我整理一下

// Replaced by CF (throws an NSException)
+ (void)dealloc {
}

// Replaced by NSZombies
- (void)dealloc {
    _objc_rootDealloc(self);
}

由这我们可以猜想: 对象在被销毁的时候, 程序会创建 Zombie对象, 调用实例方法
_objc_rootDealloc,

void
_objc_rootDealloc(id obj)
{
    显示断言, 显示被释放的对象信息
    assert(obj);

    obj->rootDealloc();
}

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    判断是否该对象应该释放
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        正式释放
        free(this);
    } 
    else {
        继续使用
        object_dispose((id)this);
    }
}

id 
object_dispose(id obj)
{
    if (!obj) return nil;
    
    在不释放内存的情况下销毁实例
    删除关联引用
    objc_destructInstance(obj);    
    
    正式销毁
    free(obj);

    return nil;
}

到这里其实还是不能看出实际的东西, 到底是什么时候被替换的, 替换的过程中做了什么, 在这里没有体现出来.

验证第二步.

刚才使用的 Runtime 源码 是.mm文件, 里面除了 OC 和 C 代码以外还包含C++代码, 苹果开源了这一部分的底层代码.

为此, 我们需要添加 符号断点, 在程序运行时, 如果有调用 __CFZombifyNSObject, 就会触发中断.

在 Zombie Objects 开启的情况下, 运行程序, 我们会发现.


NSObejct 替换了 dealloc__dealloc_zombie 这两个方法.

我们继续设置符号断点为 __dealloc_zombie. 运行程序.

大致流程如下:

小结:

维基百科-汇编语言

上一篇 下一篇

猜你喜欢

热点阅读