目录
第1章 一切都在计划之中——真正理解计算机的工作原理 1
1.1 完美的周六计划 1
1.1.1 步骤和测试 2
1.1.2 决定总是具有二元性 3
1.1.3 计算机像我们一样思考 4
1.2 如果这是真的 4
1.3 将汇编语言编程比作方块舞 4
1.4 将汇编语言编程比作棋盘游戏 5
1.4.1 代码和数据 6
1.4.2 地址 7
1.4.3 总结 7
第2章 外星人基地——理解二进制和十六进制 9
2.1 新数学怪兽的回归 9
2.1.1 使用火星文计数 10
2.1.2 剖析火星数字 12
2.1.3 数基的本质 13
2.2 八进制:鬼精灵如何偷走8和9 13
2.3 十六进制:解决数字短缺问题 17
2.4 从十六进制到十进制以及从十进制到十六进制 20
2.4.1 从十六进制到十进制 21
2.4.2 从十进制到十六进制 22
2.5 练习!练习!练习! 23
2.6 十六进制算术 24
2.6.1 列和进位 26
2.6.2 减法和借位 27
2.6.3 跨多列借位 28
2.6.4 重点是什么? 29
2.7 二进制 29
2.7.1 二进制值 31
2.7.2 为什么是二进制 33
2.8 十六进制作为二进制的简写 33
2.9 准备计算 35
第3章 揭开面纱——了解计算机的真实面貌 36
3.1 RAX寄存器,我们几乎不了解 36
3.2 开关、晶体管和内存 38
3.2.1 如果敌方陆路来袭,则点亮一盏灯 38
3.2.2 晶体管开关 39
3.2.3 令人难以置信的比特缩小现象 41
3.2.4 随机访问 42
3.2.5 内存访问时间 44
3.2.6 字节、字、双字和四字 44
3.2.7 排成一排的精美芯片 45
3.3 CPU和装配线 48
3.3.1 与内存对话 48
3.3.2 搭乘数据总线 49
3.3.3 寄存器 50
3.3.4 装配线 51
3.4 遵循计划的盒子 51
3.4.1 获取并执行 52
3.4.2 CPU的内部结构 53
3.4.3 改变路线 55
3.5 什么与如何:架构和微架构 56
3.5.1 不断演变的架构 56
3.5.2 地下室的秘密机器 57
3.6 工厂经理 58
3.6.1 操作系统:转角办公室 59
3.6.2 BIOS:软件不“软” 59
3.6.3 多任务魔法 60
3.6.4 提升至内核 61
3.6.5 内核爆炸 62
3.6.6 计划 63
第4章 寻址、寻址、寻址——寄存器、内存寻址及了解数据的位置 64
4.1 内存模型的乐趣 64
4.1.1 16位能“买到”64KB 65
4.1.2 兆字节(MB)的本质 68
4.1.3 向后兼容和虚拟86模式 69
4.1.4 16位的视野限制 70
4.2 分段的本质 71
4.2.1 一条地平线,而不是一个具体位置 74
4.2.2 使用16位寄存器生成20位地址 74
4.3 分段寄存器 76
4.3.1 分段寄存器和x64 77
4.3.2 通用寄存器 77
4.3.3 寄存器的高位和低位 79
4.3.4 指令指针 81
4.3.5 标志寄存器 82
4.3.6 数学协处理器及其寄存器 82
4.4 四种主要的汇编编程模型 83
4.4.1 实模式平面模型 83
4.4.2 实模式分段模型 85
4.4.3 32位保护模式平面模型 87
4.4.4 64位长模式编程模型 89
第5章 汇编的正确方式——汇编语言程序的开发过程 91
5.1 编程的96种方法 91
5.2 文件及其内容 92
5.2.1 二进制文件与文本文件 92
5.2.2 使用GHex十六进制编辑器查看二进制文件内部 94
5.2.3 解释原始数据 97
5.2.4 字节顺序 98
5.3 输入文本,输出代码 102
5.3.1 汇编语言 102
5.3.2 注释 104
5.3.3 当心“只写源代码”! 105
5.3.4 目标代码、链接器和库 106
5.3.5 可重定位性 108
5.4 汇编语言的开发过程 108
5.4.1 工作目录的规则 110
5.4.2 编辑源代码文件 110
5.4.3 汇编源代码文件 111
5.4.4 汇编器错误 111
5.4.5 回到编辑器 113
5.4.6 编译器警告 113
5.5 链接目标代码文件 114
5.5.1 链接器错误 115
5.5.2 测试EXE文件 115
5.5.3 错误与缺陷 116
5.5.4 调试器和调试 117
5.6 走进汇编语言的世界 117
5.6.1 安装软件 118
5.6.2 步骤1:在编辑器中编辑程序 120
5.6.3 步骤2:使用NASM汇编程序 121
5.6.4 步骤3:使用ld链接程序 123
5.6.5 步骤4:测试可执行文件 124
5.6.6 步骤5:在调试器中观察它的运行 124
第6章 一个可使用工具的立足之地——Linux和塑造你的工作方式的工具 126
6.1 集成开发环境(IDE) 126
6.2 SASM简介 128
6.2.1 配置SASM 129
6.2.2 SASM的字体 130
6.2.3 使用编译器链接 130
6.2.4 SASM速览 132
6.2.5 SASM的编辑器 134
6.2.6 SASM对你的代码有何要求 134
6.3 Linux和终端 135
6.3.1 Linux控制台 136
6.3.2 Konsole中的字符编码 136
6.3.3 三个标准UNIX文件 138
6.3.4 I/O重定向 139
6.3.5 简单文本过滤器 141
6.3.6 使用SASM内部的标准输入和标准输出 142
6.3.7 使用转义序列进行终端控制 142
6.3.8 为什么不使用GUI应用程序 143
6.4 使用Linux Make 145
6.4.1 依赖 145
6.4.2 当文件是最新的 147
6.4.3 依赖链 148
6.4.4 调用Make 149
6.4.5 为Make创建自定义按键绑定 150
6.4.6 使用touch强制构建 151
6.5 使用SASM进行调试 152
第7章 遵循你的指令——近距离观察机器指令 154
7.1 构建自己的沙箱 154
7.2 指令及其操作数 157
7.3 源操作数和目标操作数 157
7.3.1 即时数据 158
7.3.2 寄存器数据 160
7.3.3 内存数据和有效地址 162
7.3.4 混淆数据及其地址 162
7.3.5 内存数据的大小 163
7.3.6 糟糕的旧时光 164
7.4 团结在“标志”周围 164
7.4.1 标志礼仪 167
7.4.2 在SASM中观察标志 167
7.4.3 使用INC和DEC进行加1和减1操作 167
7.4.4 标志如何改变程序的执行 169
7.4.5 如何检查SASM中的变量 170
7.5 有符号值和无符号值 172
7.5.1 二进制补码和NEG 172
7.5.2 符号扩展和MOVSX 174
7.6 隐式操作数和MUL 176
7.6.1 MUL和进位标志 177
7.6.2 使用DIV进行无符号除法 178
7.6.3 MUL和DIV速度很慢 179
7.7 阅读和使用汇编语言参考 180
7.7.1 复杂记忆的备忘录 180
7.7.2 初学者的汇编语言参考 181
7.7.3 标志 181
7.8 NEG取反(二进制补码,即乘以-1) 182
7.8.1 受影响的标志 182
7.8.2 有效形式 182
7.8.3 示例 182
7.8.4 备注 182
7.8.5 有效形式 183
7.8.6 操作数符号 183
7.8.7 示例 184
7.8.8 备注 184
7.8.9 这里没有提到的内容…… 185
第8章 我们崇高的目标——创建有效的程序 186
8.1 汇编语言程序的骨架 186
8.1.1 初始注释块 188
8.1.2 .data部分 188
8.1.3 .bss部分 189
8.1.4 .text部分 189
8.1.5 标签 190
8.1.6 初始化数据的变量 191
8.1.7 字符串变量 191
8.1.8 使用EQU和$推导字符串长度 193
8.2 通过栈后进先出 194
8.2.1 每小时500个盘子 195
8.2.2 倒置堆叠 196
8.2.3 压入指令 197
8.2.4 POP指令登场 198
8.2.5 PUSHA和POPA已停用 199
8.2.6 压入和弹出的详细信息 200
8.2.7 短期存储 201
8.3 通过SYSCALL使用Linux内核服务 202
8.3.1 通过SYSCALL指令使用x64内核服务 202
8.3.2 ABI与API的区别 203
8.3.3 ABI的寄存器参数方案 203
8.3.4 通过SYSCALL退出程序 204
8.3.5 被SYSCALL破坏的寄存器 205
8.4 设计一个不平凡的程序 205
8.4.1 定义问题 206
8.4.2 从伪代码开始 207
8.4.3 持续改进 207
8.4.4 那些不可避免的惊讶时刻 211
8.4.5 扫描缓冲区 212
8.4.6 差一错误 214
8.4.7 从伪代码到汇编代码 215
8.4.8 SASM输出窗口的陷阱 218
8.4.9 进一步学习 218
第9章 位、标志、分支和表——逐步驶入汇编编程的主航道 219
9.1 位就是位(字节也是位) 219
9.1.1 位编号 220
9.1.2 最合乎逻辑的做法 220
9.1.3 AND指令 221
9.1.4 掩码位 221
9.1.5 OR指令 223
9.1.6 XOR(异或)指令 223
9.1.7 NOT指令 224
9.1.8 分段寄存器没有逻辑 225
9.2 移位 225
9.2.1 通过什么移位 226
9.2.2 移位的工作原理 226
9.2.3 将位放入进位标志 227
9.2.4 旋转指令 227
9.2.5 通过进位标志旋转位 228
9.2.6 将已知值设置到进位标志中 228
9.3 位操作实战 229
9.3.1 将一字节拆分成两个“半字节” 232
9.3.2 将高半字节移入低半字节 232
9.3.3 使用查找表 233
9.3.4 通过移位和加法进行乘法 234
9.4 标志、测试和分支 237
9.4.1 无条件跳转 237
9.4.2 有条件跳转 238
9.4.3 在缺少条件的情况下跳转 239
9.4.4 标志 240
9.4.5 使用CMP进行比较 240
9.4.6 跳转指令的“丛林” 241
9.4.7 “大于”与“高于” 241
9.4.8 使用TEST查找1位 243
9.4.9 使用BT寻找0位 244
9.5 x64长模式内存寻址详解 244
9.5.1 有效地址计算 246
9.5.2 位移 247
9.5.3 x64位移大小问题 247
9.5.4 基址寻址 248
9.5.5 基址 + 位移寻址 248
9.5.6 基址 + 索引寻址 248
9.5.7 索引×比例+位移寻址 249
9.5.8 其他寻址方案 251
9.5.9 LEA:绝密数学机器 253
9.6 字符表转换 254
9.6.1 转换表 254
9.6.2 使用MOV或XLAT进行转换 256
9.7 用表代替计算 261
第10章 分而治之——使用过程和宏来应对程序复杂性 262
10.1 层层嵌套 263
10.2 调用和返回 272
10.2.1 调用中的调用 274
10.2.2 意外递归的危险 275
10.2.3 需要警惕的标志相关错误 276
10.2.4 过程及其所需的数据 278
10.2.5 保存调用者的寄存器 278
10.2.6 在Linux系统调用中保存寄存器 280
10.2.7 PUSHAD和POPAD已废弃 281
10.2.8 本地数据 283
10.2.9 在过程定义中放置常量数据 283
10.2.10 更多表技巧 285
10.3 本地标签和跳转的长度 287
10.3.1 强制本地标签访问 290
10.3.2 短、近和远跳转 290
10.4 构建外部过程库 291
10.4.1 当工具达到极限时 292
10.4.2 在SASM中使用包含文件 292
10.4.3 SASM包含文件的存储位置 299
10.4.4 创建包含文件库的最佳方法 300
10.4.5 独立汇编和模块 301
10.4.6 全局和外部声明 301
10.4.7 全局变量和外部变量的机制 303
10.4.8 将库链接到程序中 313
10.4.9 太多过程和库会造成危险 313
10.5 制作过程的艺术 314
10.5.1 可维护性和重用性 314
10.5.2 决定什么应该是一个过程 315
10.5.3 使用注释标题 316
10.6 Linux控制台中的简单光标控制 317
10.7 创建和使用宏 325
10.7.1 宏定义的机制 326
10.7.2 定义带参数的宏 332
10.7.3 调用宏的机制 333
10.7.4 宏内的本地标签 334
10.7.5 宏库作为包含文件 335
10.7.6 宏与过程:优点和缺点 335
第11章 字符串及其他——那些令人惊叹的字符串指令 337
11.1 汇编语言字符串的概念 338
11.1.1 彻底改变你的“字符串感” 338
11.1.2 源字符串和目标字符串 339
11.1.3 文本显示虚拟屏幕 339
11.2 REP STOSB:软件机关枪 347
11.2.1 机关枪式操作虚拟显示 348
11.2.2 执行STOSB指令 348
11.2.3 STOSB和方向标志DF 349
11.2.4 定义显示缓冲区中的行 350
11.2.5 将缓冲区发送到Linux控制台 351
11.3 半自动武器:没有REP的STOSB 351
11.3.1 谁减少了RCX? 352
11.3.2 LOOP指令 352
11.3.3 在屏幕上显示标尺 353
11.3.4 MUL不是IMUL 354
11.3.5 Ruler的教训 355
11.3.6 STOS的四种大小 355
11.3.7 再见,BCD算术 356
11.4 MOVSB:快速块复制 356
11.4.1 DF和重叠阻挡移动 357
11.4.2 单步REP字符串指令 360
11.5 将数据存储到不连续的字符串中 361
11.5.1 显示一个ASCII表 361
11.5.2 嵌套指令循环 366
11.5.3 当RCX变为0时跳转 367
11.5.4 关闭内循环 367
11.5.5 关闭外循环 368
11.5.6 回顾showchar 369
11.6 命令行参数、字符串搜索和Linux栈 369
11.6.1 显示SASM的命令行参数 370
11.6.2 使用SCASB进行字符串搜索 373
11.6.3 REPNE与REPE 374
11.6.4 无法将命令行参数传递给SASM中的程序 375
11.7 栈及其结构和使用方法 375
11.7.1 直接访问栈 377
11.7.2 程序序言和结语 380
11.7.3 栈上的寻址数据 381
11.7.4 不要弹出 382
第12章 转向C——调用C语言编写的外部函数 383
12.1 GNU 384
12.1.1 瑞士军刀编译器 385
12.1.2 以GNU方式构建代码 385
12.1.3 SASM使用GCC 387
12.1.4 如何在汇编工作中使用gcc 387
12.1.5 为什么不使用gas 388
12.2 链接标准C库 389
12.2.1 C调用约定 390
12.2.2 调用者、被调用者和破坏者 390
12.2.3 设置栈帧 392
12.2.4 在结语中销毁栈帧 393
12.2.5 栈对齐 393
12.2.6 通过puts()输出字符 395
12.3 使用printf()格式化文本输出 397
12.3.1 将参数传递给printf() 398
12.3.2 printf()需要在RAX中加上前置0 399
12.3.3 你需要使用-no-pie选项 400
12.4 使用fgets()和scanf()输入数据 400
12.5 成为Linux时间领主 406
12.5.1 C库的时间机器 406
12.5.2 从系统时钟获取time_t值 407
12.5.3 将time_t值转换为格式化字符串 408
12.5.4 生成单独的本地时间值 409
12.5.5 使用MOVSD复制glibc的tm结构 409
12.6 理解AT&T指令助记符 413
12.6.1 AT&T助记符约定 413
12.6.2 AT&T内存引用语法 415
12.7 生成随机数 416
12.7.1 使用srand()为生成器设定种子 416
12.7.2 生成伪随机数 417
12.7.3 相比之下有些比特更随机 423
12.7.4 调用寄存器中的地址 425
12.7.5 使用puts()将一个裸换行符发送到控制台 426
12.7.6 如何向libc函数传递六个以上的参数 426
12.8 C语言如何处理命令行参数 427
12.9 简单文件 I/O 430
12.9.1 使用sscanf()将字符串转换为数字 431
12.9.2 创建和打开文件 432
12.9.3 使用fgets()从文件读取文本 434
12.9.4 使用fprintf()将文本写入文件 436
12.9.5 将过程收集到库中的注意事项 437
12.10 永远学习,永远不要停下来 444
12.10.1 何去何从 445
12.10.2 走出原点 446
(以下内容可扫描封底二维码下载)
附录A Insight调试器的回归 447
附录B 部分x64指令参考 454
附录C 字符集图表 509