郑重声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,如果您不同意请关闭该页面!任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!

前言

对两年前的知识进行了下温习,发现好多坑都填上了,然后把之前的文章删了总结到了一篇,到时候还有些点需要填上

Windows/Linux的内存结构

关于栈

两个系统的内容都差不多可以参考这篇文章:Linux栈溢出总结0x00

关于堆

暂时未有,等写到的时候添加

Windows的内存结构分布

首先是内存结构分布图,windows的内存默认是从0x80000000位置开始的

windows

Linux的内存结构分布

首先先放个内存结构图,linux内存默认是从0xC0000000位置开始的

linux

Windows/Linux的汇编区别

x86汇编一直存在两种不同的语法,在intel的官方文档中使用intel语法,Windows也使用intel语法,而UNIX平台的汇编器一直使用AT&T语法。而linux是UNIX衍生的一种系统所有也是使用AT&T语法。

相关区别:

  • AT&T使用 $ 表示立即操作数,而Intel的立即操作数是不需要界定的。因此,使用AT&T语法引用十进制值4时,使用$4,使用Intel语法时只需使用4
  • AT&T在寄存器名称前加上前缀 % ,而Intel不这样做。因此,使用AT&T语法引用EAX寄存器写为%eax
  • AT&T语法处理源和目标操作数时使用相反的顺序。把十进制值4传送给EAX寄存器,AT&T的语法是movl $4, %eax,而Intel语法是mov eax, 4
  • AT&T语法在助记符后面使用一个单独的字符来引用操作中使用的数据长度,而Intel语法中数据长度被声明为单独的操作数。AT&T的指令movl $test, %eax等同于Intel语法的mov eax, dword ptr test
  • 长调用和跳转使用不同语法定义段和偏移值。AT&T语法使用jmp $section, $offset,而Intel语法使用jmp section:offset

用一张表就能解释:

AT&T风格 Intel风格
寄存器前加% 寄存器无需另加符号
立即数前加$ 立即数无需另加符号
16进制立即数使用0x前缀 16进制的立即数使用h后缀
源操作数在前,目的操作数在后(从前往后读) 目的操作数在前,源操作数在后(从后往前读)
间接寻址使用小括号() 间接寻址使用中括号[]
间接寻址完整格式:%sreg:disp(%base,index,scale) 间接寻址完整格式:sreg:[basereg + index*scale + disp]
操作位数:指令+l、w、b 指令+ dword ptr、word ptr、byte ptr

CPU架构

x86(IA-32)

x86是16位和32位处理器的统称。

x86泛指一系列英特尔公司用于开发处理器的指令集架构,这类处理器最早为1978年面市的intel 8086(并不是32位的处理器,而是16位微处理器,直到1985年32位的80386才是真正的32位处理器),由于之前上市的CPU都是以86结尾的,所以被称为x86,但是数字没办法注册称为商标,最后将其IA-32,全名为“Intel Architecture, 32-bit”。IA-32在第三代x86架构才使用上,也就是真正意义上的32位处理器

image-20201103100725731

IA-64

IA-64,又称英特尔安腾架构(Intel Itanium architecture),使用在Itanium处理器家族上的64位元指令集架构,由英特尔与惠普共同开发。此架构与x86及x86-64并不相容,操作系统与软件需使用IA-64专用版本

第一款安腾于2001年推出,2017年推出最后一代安腾处理器,并停止开发。

x86-64(AMD64)

目前市面上除了英特尔安腾的64位CPU,其他Intel和AMD的支持64位的CPU都是使用这个架构的。

x86-64( 又称x64,即英文词64-bit extended,64位拓展 的简写)是x86架构的64位拓展,向后兼容于16位及32位的x86架构。x64于1999年由AMD设计,AMD首次公开64位集以扩展给x86,称为“AMD64”。其后也为英特尔所采用,现在英特尔称之为“Intel 64”,在之前曾使用过“Clackamas Technology” (CT)、“IA-32e”及“EM64T”来称呼。

其他各类叫法:

  • 苹果公司和RPM包管理员以“x86-64”或“x86_64”称呼此64位架构。

  • 甲骨文公司及Microsoft称之为“x64”。

  • BSD家族及其他Linux发行版则使用“amd64”,32位版本则称为“i386”(或 i486/586/686)。

  • Arch Linux用x86_64称呼此64位架构。

ARM

ARM架构,过去称作高级精简指令集机器(英语:Advanced RISC Machine),是一个精简指令集(RISC)处理器架构家族,其广泛地使用在许多嵌入式系统设计。常见的ARM架构CPU设备:树莓派(Broadcom)、手机(HiSilicon、Qualcomm)、大型工作站、超级计算机

架构 处理器家族
ARMv1 ARM1
ARMv2 ARM2、ARM3
ARMv3 ARM6、ARM7
ARMv4 StrongARM、ARM7TDMI、ARM9TDMI
ARMv5 ARM7EJ、ARM9E、ARM10E、XScale
ARMv6 ARM11、ARM Cortex-M
ARMv7 ARM Cortex-A、ARM Cortex-M、ARM Cortex-R
ARMv8 Cortex-A35、Cortex-A50系列、Cortex-A72、Cortex-A73

截止2020年CPU架构种类

image-20201103112307820

PowerPC

PowerPC(英语:Performance Optimization With Enhanced RISC – Performance Computing,有时简称PPC)是一种基于精简指令集(RISC)的指令集架构 ISA(Instruction set architecture),其基本的设计源自IBM的POWER(Performance Optimized With Enhanced RISC)架构。PowerPC是1991年,Apple、IBM、Motorola组成的AIM联盟所发展出的微处理器架构。PowerPC是整个AIM联盟平台的一部分,并且是到目前为止唯一的一部分。但苹果电脑自2005年起,将旗下电脑产品转用Intel x86。

较广为人知的产品应用包含:

  • 苹果公司:Power Macintosh系列、PowerPC PowerBook系列(1995年以后的产品)、iBook系列、iMac系列(2005年以前的产品)、eMac系列产品。
  • 任天堂:GameCube、Wii和Wii U(之后换成了ARM架构处理器)。
  • 微软:Xbox 360(之后的产品换成了AMD的处理器)
  • 索尼:PlayStation 3(之后的产品换成了AMD的处理器)

MIPS

MIPSMicroprocessor without Interlocked Pipeline Stages),是一种采取精简指令集(RISC)的指令集架构(ISA),1981年出现,由 MIPS 公司开发。最早的MIPS架构是32比特,最新的版本已经变成64比特。

MIPS 架构有多个版本。其中包括 MIPS I、II、III、IV,以及 MIPS V,早期的 MIPS 架构只有 32 位的版本,而其 64 位的版本随后才被开发。截至 2017 年 4 月,MIPS32/64 的当前版本是 MIPS32/64 Release 6。MIPS32/64 与

相关产品

  • 北京君正集成电路股份有限公司:Xburst系列微架构半导体IP核
  • 中国龙芯系列产品

LoongISA

LoongISA(简称LISA)是龙芯中科注册的自主CPU指令集架构(由MIPS指令集拓展而来,采用MIPS的指令集格式)。LoongISA 指令集架构包括MIPS64 Release 2全套指令集和MIPS64 Release 5中的部分指令模块,以及其他一系列龙芯中科自主扩展的指令集。

SPARC

SPARC,名称源自于可扩展处理器架构Scalable Processor ARChitecture)的缩写,是一种RISC指令集架构,最早于1985年由Sun微系统所设计,也是SPARC国际公司的注册商标之一。

RISC-V

RISC-V(发音为“risk-five”)是一个基于精简指令集(RISC)原则的开源指令集架构(ISA),简易解释为开源软件运动相对应的一种“开源硬件”。该项目2010年始于加州大学柏克莱分校,但许多贡献者是该大学以外的志愿者和行业工作者。RISC-V指令集可以自由地用于任何目的,允许任何人设计、制造和销售RISC-V芯片和软件而不必支付给任何公司专利费。

X86架构汇编

Intel汇编和AT&T汇编的区别

  • Intel汇编
    • DOS、Windows,包括我们之前了解的8086处理器
    • Windwos派系:VC编译器
  • AT&T汇编
    • Linux、Unix、Mac OS、iOS模拟器
    • Unix派系:GCC编译器

以下使用的是Intel汇编语法讲解

32位CPU所含有的寄存器

  • 8个32位通用寄存器
    • 4个数据寄存器(EAX、EBX、ECX和EDX)
    • 2个变址和指针寄存器(ESI和EDI) 2个指针寄存器(ESP和EBP)
  • 6个段寄存器(ES、CS、SS、DS、FS和GS)
  • 1个指令指针寄存器(EIP)
  • 1个标志寄存器(EFlags)

每个寄存器都可作为一个32位值或两个16位值来寻址使用。某些16位的寄存器能够按照8位值寻址使用。

32 16 高8 低8
EAX AX AH AL
EBX BX BH BL
ECX CX CH CL
EDX DX DH DL

下面几个没有8位模式:

32 16
ESI SI
EDI DI
EBP BP
ESP SP

EAX的低16位称为AX,AX的高8位称为AH,低8位称为AL。

举个例子

寄存器EAX存储了值0xA9DC81F5,代码可以使用其他三种方式来引用EAX中的这个数据:AX(2字节)是0x81F5,AL(1字节)是0xF5,AH(1字节)是0x81,如图下图所示

image-20200831133326546

数据寄存器

数据寄存器主要用来保存操作数和运算结果等信息,从而节省读取操作数所需占用总线和访问存储器的时间。

32位CPU有4个32位的通用寄存器EAX、EBX、ECX和EDX。

  • 对低16位数据的存取,不会影响高16位的数据。
  • 寄存器EAX通常称为累加器(Accumulator),用累加器进行的操作可能需要更少时间。可用于乘、 除、输入/输出等操作,使用频率很高;
  • 寄存器EBX称为基地址寄存器(Base Register)。它可作为存储器指针来使用;
  • 寄存器ECX称为计数寄存器(Count Register)。 在循环和字符串操作时,要用它来控制循环次数;在位操作中,当移多位时,要用CL来指明移位的位数;
  • 寄存器EDX称为数据寄存器(Data Register)。在进行乘、除运算时,它可作为默认的操作数参与运算,也可用于存放I/O的端口地址。

关于乘法和除法注意点:

  • 如果乘法的结果过大,会把结果分别存入EDX和EAX寄存器中,EDX存储高32位数据,EAX存储低32位数据

  • 如果要做除法之前,先要把除数赋值到EDX和EAX寄存器中,然后将结果存入EAX中,余数存入EDX中

指令 描述
mul 0x50 EAX值乘以0x50,并将结果存入EDX:EAX寄存器中
div 0x75 将EDX:EAX值除以0x75,并将结果存入EAX,将余数存入EDX
变址寄存器

32位CPU有2个32位通用寄存器ESI和EDI。

寄存器ESI、EDI、SI和DI称为变址寄存器(Index Register),它们主要用于存放存储单元在段内的偏移量, 它们可作一般的存储器指针使用。

指针寄存器

32位CPU有2个32位通用寄存器EBP和ESP。

它们主要用于访问堆栈内的存储单元,并且规定:

  • EBP为基指针(Base Pointer)寄存器,一般作为当前堆栈的最后单元,用它可直接存取堆栈中的数据;

  • ESP为堆栈指针(Stack Pointer)寄存器,用它只可访问栈顶。

指令指针寄存器

32位CPU把指令指针扩展到32位,并记作EIP

指令指针EIP、IP(Instruction Pointer)是存放下次将要执行的指令在代码段的偏移量。

标志寄存器

32位CPU的标志寄存器为EFLAG

CF:进位标志
OF:溢出标志
SF:符号标志
ZF:零标志
AC:辅助进位标志
PF:奇偶标志

寄存器

以下使用的是AT&T汇编语法讲解

数据格式

1

整数寄存器

X86-64中央处理单元(CPU)中有一组16个存储64位值的通用目的寄存器

初代8086中有816位的寄存器(%ax-%sp)

扩展带IA32架构时变成了832位寄存器(%eax-%esp)

扩展到X86-64架构时变成1664位寄存器(%rax-%rsp,%r8-%r15)前8个寄存器是做了扩充,后8个寄存器是新加的

2

16位操作可以访问最低的2个字节,32位操作可以访问最低的4个字节,而64位操作可以访问整个寄存器。

操作数指示符

1

例题:

1

答案:

1

指令

数据传送
普通的数据传送如图

1

做了零扩展的数据传送

11

做了符号扩展的数据传送

11

图中给了一个cltq指令,这条指令没有操作数:它总是以寄存器%eax作为源,%rax作为符号扩展结果为目的(它的效果与指令movslq %eax,%rax完全一致,不过编码更紧凑)

movb&movsbq&movzbq之间的差别:
movabsq   $0x0011223344556677,%rax                     # %rax=0011223344556677
movb $0xAA,%dl # %dl=AA
movb %dl,%al # %RAX=00112233445566AA
movsbq %dl,%rax # %rax=FFFFFFFFFFFFFFAA
movzbq %dl,%rax # %rax=00000000000000AA
#movb指令不改变其他字节
#movsbq将其他7个字节设为全1或全0,由于十六进制A表示二进制值1010,符号扩展会把高位字节都设置为FF
#movzbq讲其他7个字节都设置为0
压入和弹出栈数据

1

将一个四字值压人栈中,首先要将栈指针减8,然后将值写到新的栈顶地址。因此,指令pushq %rbp的行为等价于下面两条指令:

subq           $8,%rsp
movq %rbp,(%rsp)

弹出一个四字的操作包括从栈顶位置读出数据,然后将栈指针加8。因此,指令popq %rax的行为等价于下面两条指令:

movq          (%rsp),%rax
addq $8,%rsp
算术和逻辑操作

这些操作被分成4组:加载有效地址,一元操作,二元操作和移位。二元操作有两个操作数,而一元操作有一个操作数。

1

加载有效地址

指令leaq实际上是movq指令的变形,它的指令形式是从内存读数据到寄存器,但实际上它根本没有引用内存。

例子:

假设寄存器%rax的值为X%rcx的值为y,结果为%rdx中的值
11

一元和二元操作

这里没有什么可以写的直接上例题吧

例题:

1

答案:

1

位移操作

左移指令有两个名字:SALSHL。两者的效果都是一样的,都是将右边填上0

右移指令有两个名字:SARSHR。但是效果不同SAR执行算术移位(填上符号位),SHR执行逻辑移位(填上0)

特殊的算术操作

111

条件码
  • CF:进位标志。最近的操作使最高位产生了进位。可以用来检查无符号操作数据的溢出。

  • ZF:零标志。最近的操作得出的结果为0.

  • SF:符号标志。最近的操作得到的结果为负数。

  • OF: 溢出标志。最近的操作导致一个补码溢出–正溢出或负溢出。

  • 比较和测试指令

    111

    CMPTEST的区别:
    test逻辑与运算结果为零,就把ZF(零标志)置1
    cmp 算术减法运算结果为零,就把ZF(零标志)置1

  • 访问条件码

    直接看图

    11

  • 跳转指令

    111
    直接跳转:跳转目标是作为指令的一部分编码。写法jmp .L1

    间接跳转:跳转目标是从寄存器或内存位置中读出。写法jmp *%rax或者jmp *(%rax)

    用寄存器%rax中的值作为跳转目标

    jmp *%rax
    %rax中的值作为读地址,从内存中读出跳转目标

    jmp *(%rax)

  • 转移控制

    11

    call指令有一个目标,指明被调用过程起始的指令地址。同跳转一样,也有直接和间接区别。直接调用的目标是一个标号,而间接调用的目标是*后面跟一个操作数指示符。

  • 条件传送

    是传送指令的变形,用法和跳转指令类似

    1

关于指针

指针类型不是机器代码的一部分
每个指针都是一个值,这个值是某个指定类型的对象地址。

浮点代码

后续在更新

后话

关于数组,栈,一些像if,switch等书上讲的更详细

MinGW-w64中包含的工具

在GNU/Linux和UNIX系统中,MinGW-w64提供的命令以${arch}-w64-mingw32-作为前缀,这是因为这些工具本身是从GCC、GNU BinUtils等项目移植的, 如果不加前缀的话,会与这些软件的命令互相覆盖。

这些命令的用法与GCC、GNU BinUtils等项目中的命令的用法完全一样。

arch bit
i686 32bit
x86_64 64bit
命令名称 底层 对应软件
${arch}-w64-mingw32-gcc GCC gcc
${arch}-w64-mingw32-gcc-ar GCC gcc-ar
${arch}-w64-mingw32-gcc-nm GCC gcc-nm
${arch}-w64-mingw32-gcc-ranlib GCC gcc-ranlib
${arch}-w64-mingw32-g++ GCC g++
${arch}-w64-mingw32-gcov GCC gcov
${arch}-w64-mingw32-gcov-dump GCC gcov-dump
${arch}-w64-mingw32-gcov-tool GCC gcov-tool
${arch}-w64-mingw32-c++ GCC c++
${arch}-w64-mingw32-cpp GCC cpp
${arch}-w64-mingw32-as BinUtils as
${arch}-w64-mingw32-ld BinUtils ld
${arch}-w64-mingw32-ar BinUtils ar
${arch}-w64-mingw32-ranlib BinUtils ranlib
${arch}-w64-mingw32-nm BinUtils nm
${arch}-w64-mingw32-strip BinUtils strip
${arch}-w64-mingw32-c++filt BinUtils c++filt
${arch}-w64-mingw32-objcopy BinUtils objcopy
${arch}-w64-mingw32-objdump BinUtils objdump
${arch}-w64-mingw32-readelf BinUtils readelf
${arch}-w64-mingw32-gprof BinUtils gprof
${arch}-w64-mingw32-strings BinUtils strings
${arch}-w64-mingw32-size BinUtils size
${arch}-w64-mingw32-addr2line BinUtils addr2line
${arch}-w64-mingw32-nlmconv BinUtils nlmconv
${arch}-w64-mingw32-dlltool BinUtils dlltool
${arch}-w64-mingw32-windmc BinUtils windmc
${arch}-w64-mingw32-windres BinUtils windres

使用x86_64-w64-mingw32-gcc命令编译

x86_64-w64-mingw32-gcc -o hello.exe hello.c

参考文章

https://www.cnblogs.com/nufangrensheng/p/3893272.html
https://blog.csdn.net/lpwstr/article/details/78817831
《深入了解计算机系统》
https://wikipedia.org