分销系统网站建设WordPress评论加签到

当前位置: 首页 > news >正文

分销系统网站建设,WordPress评论加签到,微信小程序毕业设计,中国建设银行网站下载安装1. 前置知识

  1. 汇编语言两种风格 intel#xff1a;我们学的NASM就属于Intel风格ATT#xff1a;GCC后端工具默认使用这种风格#xff0c;当然我们也可以加选项改成intel风格
  2. 代码
  3. 段分布 .text: 存放的是二进制机器码#xff0c;只读.data: 存放有初始化的…1. 前置知识
  4. 汇编语言两种风格 intel我们学的NASM就属于Intel风格ATTGCC后端工具默认使用这种风格当然我们也可以加选项改成intel风格
  5. 代码
  6. 段分布 .text: 存放的是二进制机器码只读.data: 存放有初始化的全局变量。非默认值.bss存放未初始化的全局变量或者默认初始化的全局变量。这一部分在二进制文件中不占硬盘空间即不会真实存储这些为初始化的变量而是在程序加载到内存时再分配。当然肯定需要有个标识告诉该怎么分配内存.rodata存放只读数据如常量数据 #include stdio.hint global_var1; // bss int global_var2 0; // bss int global_var3 42; // data const char* hello Hello, world!; // rodataint main() // text {printf(%s\n, hello); // textreturn 0; // text }2. 标签 section .data counter db 10 ; 声明并初始化计数器counter表示计数器的地址section .text global _start_start:nop ; No operation, 用于占位loop_start: ; 标签定义一个循环开始的位置dec byte [counter] ; 减少计数器的值jnz loop_start ; 如果计数器非零ZF0跳回到 loop_start; … 程序的其余部分 标签可以被视为一个给定位置的名称或者是一个指向特定地址的指针。 可以把他们都当作地址上面的例子定义了两个标签:counter、loop_start本地标签本地标签通常以.开头只在本地上下文有意义
  7. 包含 section .data %include data.asmsection .text global _start _start:%include code.asminclude通常用于在一个源文件中插入另一个源文件的内容,等同于C中的#include上面的例子就是将 data.asm的内容插入到.data段 section .dataimage: incbin picture.bmpincbin:此伪指令可以用于将一些预先生成的、需要嵌入到程序的二进制数据如图像、音频、编码的数据文件等直接加载到汇编程序中。其格式为incbin “filename” [, skip, length]
  8. 内存
  9. 寄存器(在x64架构中) 通用寄存器 RAX: 累加器。用于进行算数运算同时也是一部分系统调用例如sys_write或sys_read中存储返回值的寄存器。RBX: 基址寄存器。一般用于在间接寻址中保存变量的内存地址。RCX: 计数器寄存器。在循环迭代中常常用作循环计数。RDX: 数据寄存器。通常与RAX配合用于大数的乘法和除法运算。RSI: 源变址寄存器。在字符串和内存操作中常常用来存储源地址。RDI: 目的变址寄存器。在字符串和内存操作中常常用来存储目的地址。RBP: 基址指针。通常被用作帧指针指示当前函数帧在堆栈中的位置。RSP: 堆栈指针。始终指向当前栈顶的位置。R8 - R15: 在x64架构中新增的8个通用寄存器可以通用性地使用。 对于这些寄存器的使用取决于具体的应用场景和编程约定。例如某些系统调用可能会使用RDIRSIRDXR10R8和R9来传递前6个参数。以上寄存器都是64位 指令寄存器 RIP指令指针指向下一条要执行的指令。64位 段寄存器(都是16位) CSCode Segment代码段寄存器包含当前正在执行的代码的段基址。DSData Segment数据段寄存器通常包含程序正在操作的数据的段基址。SSStack Segment堆栈段寄存器包含当前堆栈的段基址。ESExtra Segment附加段寄存器用于存储其他数据段的基址。FSGS 标志寄存器 RFLAGS64位标志寄存器保存了程序的运行状态
    一个简单的汇编例子如下 section .data msg db Hello, World!, 0section .text global _start_start:; syscall: writemov eax, 1 ; sys_writemov edi, 1 ; file descriptor: stdoutlea rsi, [rel msg] ; buffer address: msgmov edx, 13 ; message lengthsyscall ; perform syscall; syscall: exitxor edi, edi ; exit status code:2. 寻址 在 x64 架构中使用 NASM 汇编支持的寻址方式如下 立即寻址Immediate addressing操作的数据直接包含在指令中。比如 mov rax, 123 ; rax 123寄存器寻址Register addressing操作数存储在寄存器中。例如 add rax, rbx ; rax rax rbx直接寻址Direct addressing操作的数据在内存中的某个具体位置其地址在指令中直接给出。例如 mov eax, [someVariable] ; eax contents of memory at address someVariable间接寻址Indirect addressing操作数存储在由另一个寄存器指定的内存地址中。例如 mov eax, [rbx] ; eax contents of memory at address stored in rbx基址寻址Base addressing操作数的地址是某个寄存器的值加上一个常量偏移量。例如 mov eax, [rbx4] ; eax contents of memory at address (rbx 4)索引寻址Indexed addressing操作数地址由基址加上索引值乘以元素大小给出。它通常被用来处理数组。例如 mov eax, [rbxrcx*4] ; eax contents of memory at address (rbx rcx*4)基址变址寻址Base-indexed addressing使用一个基址加上一个偏移量再加上一个索引。例如 mov eax, [rbxrcx4] ; eax contents of memory at address (rbx rcx 4)以上就是在 NASM 中使用的基于 x64 架构的寻址方式的简单介绍。选择哪种寻址方式取决于你需要在哪里获取数据或者指令应该如何计算操作数的地址。
  10. 指令
  11. 操作数类型 立即数(imm)以常量出现在指令中只能是源操作数寄存器(reg)数据存放在寄存器中指令中给出寄存器名内存(mem)数据存在于内存单元中指令中给出内存地址
  12. 操作 数据移动指令 mov复制移动源操作数到目标操作数。push将数据压入堆栈。pop从堆栈中弹出数据。 算数指令 add两个操作数相加结果保存到目标操作数中。sub两个操作数相减结果保存到目标操作数中。inc将操作数的值加一。dec将操作数的值减一。imul相乘操作。idiv相除操作。 逻辑运算指令 and逻辑与操作。or逻辑或操作。xor逻辑异或操作。not逻辑非操作。 控制流指令 jmp无条件跳转到指定的代码地址。je, jne, jg, jge, jl, jle基于某个条件跳转。call调用一个子程序/函数通常会跳转到一个代码地址并将返回地址压入堆栈以方便返回。ret从子程序/函数返回通常会弹出一个值作为下一个要执行的代码地址。loop 循环指令 比较和测试指令 cmp比较两个操作数。test逻辑与测试。 字符串操作指令 movs, cmps, scas, lods, stos对字符串进行操作的一组指令。 转换指令 cwdecdqecwdcdqcqo转换字宽度的一组指令。
  13. 数据 High Addresses — .———————-.| Environment ||———————-|| | Functions and variable are declared| STACK | on the stack. base pointer - | - - - - - - - - - - -|| | || v |: :. . The stack grows down into unused space. Empty . while the heap grows up. . .. . (other memory maps do occur here, such . . as dynamic libraries, and different memory: : allocate)| ^ || | |brk point - | - - - - - - - - - - -| Dynamic memory is declared on the heap| HEAP || ||———————-|| BSS | Uninitialized data (BSS)|———————-| | Data | Initialized data (DS)|———————-|| Text | Binary code Low Addresses —- ———————-1. 内存分段 在汇编语言层面变量主要体现为内存的确定位置。 在 NASM 汇编中你可以通过 SECTION 或者 SEGMENT 关键字把变量定义在不同的段区。以下是一些常见的段区 .data 段用于存储程序中已初始化的全局变量和静态变量。SECTION .data var1 db 10 ; 定义一个字节变量var1并初始化值为10 var2 dd 1000 ; 定义一个双字变量var2并初始化值为1000 arry db 1, 2, 3, 4, 5 ; 定义了一个长度为5的字节数组在访问时通过基址加上偏移的方式访问各元素。.bss 段用于声明未初始化的全局变量和静态变量这些变量在程序开始执行前自动初始化为0。声明变量使用 resb, resw, resd, resq 等伪指令。SECTION .bss var3 resb 1 ; 定义一个字节变量var3 var4 resd 1 ; 定义一个双字变量var4 b resq 2. ; 2个DQ空间 blen equ $ - b. ; resq*216.text 段是程序代码段会包含程序的可执行指令。SECTION .text global _start ; 声明一个全局入口 _start:; 你的汇编代码.rodata 段用于存储只读数据比如你的程序中的常量字符串。SECTION .rodata msg db Hello, World, 0 ; 定义一个字符串常量msg。 每个字符一个字节0在c语言表示字符串结尾 screenWidth equ 1024 ; 定义一个符号名字叫做screenWidth代表数值为1024x86/x64架构采用小端数据存储var dd 0x12345678 这个四字节变量在内存中从低位地址到高位地址依次是0x78,0x56,0x34,0x12。在访问大于一个字节的内存时需要小心处理字节的顺序。变量定义在 .bss和.data段中常量在.rodata段中
  14. 声明内存分配的伪指令 dbDefine Byte定义一个字节大内存空间myConst db 12 ; 声明一个名为 myConst 的字节常量值为12dwDefine Word定义一个两字节大的内存空间。myConst dw 1234 ; 声明一个名为 myConst 的字常量值为1234ddDefine Double Word定义一个四字节大的内存空间。var dd 12345678 ; 声明一个名为var 的双字变量值为12345678dqDefine Quad Word定义一个八字节大的内存空间。myConst dq 123456789012345678 ; 声明一个名为 myConst 的四字常量值为123456789012345678dtDefine Ten Bytes定义一个十字节大的内存空间。
  15. equ伪指令 equ 在 NASM 汇编中用于声明一个符号。在汇编阶段这些由 equ 定义的符号将被替换为它们代表的实际值。类似于在高级编程语言中我们定义的预处理宏或者常量。它是伪指令不占内存 例如如果你写下以下代码 PI equ 3.14 times 2*PI在汇编阶段这将被视为 times 2*3.14这样的特性使得 equ 成为定义常量值、内存大小或其他需要在编译阶段进行代替的符号的理想选择。 equ 定义的是符号以及其对应的值不分配内存db, dw, dd, dq 定义的是内存中的数据即变量或者说是内存分配的常量会占用实际的内存空间。
  16. times 伪指令 在 NASM 汇编中times 是一种伪指令它的功能是重复指定的汇编指令或数据定义指定的次数。 数据段中的使用times 可以用来定义重复的数据。 section .data ones db 1 ; 定义一个字节的数据大小为1 bigspace times 1000 db 0 ; 定义一个包含1000个字节的数据每个字节初始化为0代码段中的使用times 可以用来重复指定的汇编指令。 section .text global _start _start:times 5 mov al, A ; 执行5次mov al, A 指令注意上述代码段中的用法仅作为times概念理解实际上这段代码并没有意义因为连续执行相同的mov指令并不会有特别的效果因为每次执行都会覆盖原有内容。在实际编程中times通常用于数据定义如初始化一个具有相同初始值的大数组。 总的来说times 是一种在汇编期间进行循环展开的方法它可以用来定义大块的重复数据或者重复执行同一条指令。
  17. \(和\)\(符号的含义 \)\( 表示当前指令的地址。这在给向前或者向后跳转的指令填写距离的时候很有用因为你可以用 \) 来代表当前的位置然后和目标指令的标签计算距离。 mov eax, \( ; 将当前指令的地址赋给 eax\)$$\( 表示当前段的开始地址。它通常用来计算相对于段开始的偏移。这在一些需要处理相对地址的操作中比较有用。 mov eax, \)$ ; 将当前段开始的地址赋给 eax6. %assign和%define宏与处理器命令 在 NASM 汇编中%assign 和 %define 是宏预处理器指令用于定义和赋值符号。 %define是用来定义一个宏常量或者宏。定义后当汇编器在后续的代码中遇到这个宏时就会用其定义的字符串替换。典型用法如下 %define BUFFER_SIZE 1024 mov eax, BUFFER_SIZE ; 预处理阶段会替换成 mov eax, 1024%assign是用来给一个符号赋值一个表达式然后可以在后续中用这个符号来引用这个表达式。典型用法如下 %assign BUFFER_SIZE 1024 mov eax, BUFFER_SIZE ; 预处理阶段会替换成 mov eax, 1024两者的主要区别在于 %assign 通常用于数值或者可以计算的表达式而 %define 更多的是用于字符串替换。比如你可以在 %assign 中写一个算术表达式而 %define 则不解析它的内容直接做文本替换。 %assign SIZE 1024 * 2 ;size2048 %define SIZE_DEF 1024 * 2 ;size1024 * 27. 前缀指令rep rep 是一种前缀指令通常与串操作指令如 movs, stos, cmps, scas, lods 等一起使用用于对字符串进行重复操作。 rep 指令的功能是只要 cx 或 ecx根据地址大小16位环境下使用 cx32位环境下使用 ecx的值不为0就重复执行后面的指令并且每执行一次cx 或 ecx 的值自减1。
    下面是一个例子使用 rep 前缀指令来赋值字符串前四个字节。假设我们要将字符串的前四个字节赋值为 A section .data buffer db 12345678 ; 原始字符串section .text global _start _start:mov ecx, 4 ; 要复制的字节数量mov al, A ; 要复制的字节值lea edi, [buffer] ; 目标字符串的地址rep stosb ; 执行赋值操作; 假设用 _start 函数作为程序的起点; 你可以在这里添加你想要的代码,; 比如将更改后的字符串打印到控制台。; 程序退出mov eax, 0x60xor edi, edi ; 参数syscall ; 调用系统退出rep stosb 指令的作用是将 AL 寄存器的内容存储到由 EDI 寄存器指向的内存位置然后 EDI 将根据标志寄存器中的方向标志Direction Flag增加或减少。如果方向标志为 0默认EDI 加1如果为 1EDI 减1。然后 ECX 减 1。当 ECX 变为 0 时rep 指令就会停止重复。因此上述代码的效果就是将 A 复制4次到 buffer。 此外还有两个与 rep 类似的重复前缀 repe 或 repz只要 zx 标志零标志为真并且 cx 或 ecx 的值不为 0就重复执行后面的指令。这种情况常与 cmpsb, cmpsw, 或 scas 等指令配合使用。 repne 或 repnz只要 zx 标志零标志为假并且 cx 或 ecx 的值不为 0就重复执行后面的指令。这种情况常与 cmpsb, cmpsw, 或 scas 等指令配合使用。
  18. 结构体如何实现 在汇编语言中并没有直接定义结构体的语法但是我们可以使用数据定义指令如 db, dw, dd, dq以及各种标签和偏移来创建类似结构体的复合数据类型。 下面是在 NASM 汇编语言中定义和操作结构体的一个例子 例如我们要定义一个类似 C 语言中的如下结构体 struct MyStruct {int id;char name[10]; };在汇编中可以这样定义 section .dataMyStruct: .id dd 1 ; 4 bytes for integer value.name db nasm ; 10 bytes for char array.endStruct然后我们可以使用标签定位每个元素并对其进行操作。例如我们可以改变 id 的值 mov [MyStruct.id], dword 2或者改变 name 的值 mov [MyStruct.name], byte x在 NASM 汇编中. 前缀被用于定义一个局部标签这个局部标签只在距离它最近的上一个非局部标签没有以.开头的标签之后可见。 在上面例子中 .id 和 .name 是 MyStruct 的局部标签。在 MyStruct 之后的代码中你可以通过 MyStruct.id 访问 id 域通过 MyStruct.name 访问 name 域。当然你还可以定义一个新的结构并且使用相同的局部标签名。例如 Section .dataMyStruct: .id dd 1.name db nasmYourStruct:.id dd 2.name db assembly在这个例子中.id 和 .name 在YourStruct中重新开始且不会引起重定义错误。你可以通过YourStruct.id和YourStruct.name来访问这个新结构的字段。这种方式使代码更加结构化易于理解和维护。
  19. 函数
  20. 宏的定义 在汇编语言中我们可以使用 %macro 和 %endmacro 指令来定义一个宏。宏可以包含任意的代码片段并用一个自定义的名字来标识它然后可以在需要的地方通过这个名字来使用宏。 以下是一个在 NASM 汇编中定义和使用宏的例子 ; 定义宏 %macro print 1mov eax, 4 ; sys_write 的系统调用号为4mov ebx, 1 ; 文件描述符 1 —— stdoutmov ecx, %1 ; 要打印的字符串mov edx, %1len ; 要打印的字符串长度int 0x80 ; 执行系统调用 %endmacrosection .data Msg db Hello, World!,0xA ; 0xA 不包括在长度计数中 Len equ $-Msgsection .text global _start _start:print Msg ; 使用宏mov eax, 1xor ebx, ebxint 0x80在此示例中我们定义了一个名为 print 的宏该宏接收一个参数用 %1 表示将这个参数打印到 stdout。我们可以通过将字符串名传递给 print 宏来使用它就像在 _start: 标签下那样。 注意%macro 指令之后的 1 表示该宏需要一个参数。如果你的宏需要多个参数你可以通过改变这个数字而且可以用 %1、%2 等来访问这些参数。 此外%1len 是 NASM 的一个特性它可以返回宏参数的长度这在处理字符串时非常有用。当我们传递 Msg 作为参数到 print时%1len 就被替换成了 Msg 的长度也就是字符串 ‘Hello, World!’ 的字符数。这个长度在编译期就被确定下来不会在运行期改变。
  21. 规定宏的参数个数 ; 定义一个接受两个参数的宏 %macro sum 2mov eax, %1add eax, %2 %endmacrosection .text global _start _start:sum 5, 3 ; 调用宏参数为5和3在此示例中sum是一个宏它接受两个参数表示为%1和%2。此宏将第一个参数和第二个参数相加然后将结果存入eax寄存器。 使用sum 5, 3%1是5
  22. 宏的重载 在汇编语言中特别是在 NASM 中没有直接支持宏重载的功能。 有一些间接的方法可以模拟宏重载的效果。一种常见的方法是根据参数数量的不同来改变宏的行为。NASM 这样支持 %macro say 0-2 ; 0-2 表示该宏可以接受0个1个或2个参数%if %0 0 ; %0 用来获取宏参数的数量%define msg Hello, World! %elif %0 1%define msg %1 %else%define msg Too many arguments %endifmov eax, 4mov ebx, 1mov ecx, msgmov edx, msglenint 0x80%endmacro如上的代码say 宏可以接受0个、1个或2个参数并根据参数数量的不同采取不同的行为实际上就模拟了宏重载的效果。 不过要注意这样的方法在复杂的程序中可能会导致代码难以理解和维护所以应当谨慎使用。 在需要进行底层编程的时候如果有“宏重载”的需求可能需要考虑使用更高级的语言或者 assembly 的宏处理工具例如使用 C/C 可以更便捷地处理此类需求。
  23. 宏中使用本地标签%% 在 NASM 汇编中本地标签或称局部标签常常在宏定义中使用主要用来创建在宏内部唯一但在宏的实例之间不唯一的标签名。 对于宏内部的代码流程控制我们通常需要使用跳转指令而跳转指令又需要标签。如果我们在不同的宏或者宏的多个实例中使用相同名称的标签就会造成标签重复定义的问题。为了解决这个问题NASM 提供了一个特性允许我们在宏定义中使用 %% 前缀来创建本地标签。 以下是一个在 NASM 中创建和使用本地标签的例子 %macro do_something 0mov eax, 1%%loop: ; 一个本地标签add eax, eaxcmp eax, 1024jl %%loop ; 使用本地标签 %endmacrosection .text global _start _start:do_something ; 调用宏第一次do_something ; 调用宏第二次在这个例子中我们定义了一个名为 do_something 的宏该宏包含一个本地标签 %%loop 和一个跳转指令 jl该跳转指令在 eax 小于1024时跳转到 %%loop 标签处执行代码。我们可以多次调用这个宏每次调用这个宏时宏内的 %%loop 都为新生成的代码块创建一个新的唯一的标签。 使用本地标签我们可以在宏定义中包含更复杂的控制流程而不需要担心标签重复定义的问题。同时由于标签名在不同宏实例中可以相同这也使得我们可以编写更通用更有复用性的宏。
  24. 函数运行栈 call: 将IP寄存器内容入栈跳转到目标函数ret弹出值到IP寄存器跳转回调用方继续执行
  25. 函数调用 在Linux下函数的参数主要通过RDI, RSI, RDX, RCX, R8, R9 这些寄存器传递如果参数多于这些寄存器的数量那么剩余的参数会通过堆栈传递。函数的返回值通常通过RAX寄存器返回。 以下是一个在 NASM 中的示例包含定义和调用函数 section .data value1 dq 5 value2 dq 3 result dq 0section .text global _start _start:; 把参数放在寄存器中mov rdi, [value1]mov rsi, [value2]; 调用函数call add_two_numbers; 函数返回后结果在 rax 寄存器中mov [result], rax; 退出程序mov eax, 60xor edi, edisyscall; 函数: add_two_numbers ; 参数: rdi num1, rsi num2 ; 返回: rax num1 num2 add_two_numbers:; 函数体add rdi, rsimov rax, rdiret ; 返回在上述代码中我们定义了一个函数add_two_numbers它接受两个参数返回它们的和。函数的参数通过rdi和rsi寄存器传递返回值存储在rax寄存器中。call指令用于执行函数调用ret指令用于从函数返回。
  26. 函数的保护现场处理 在 NASM 风格的 x86-64也叫 AMD64汇编中保存现场和恢复现场的代码示例如下 section .text global _start _start:; 保存现场push raxpush rbx; 这里是函数调用等操作; …; 恢复现场pop rbxpop rax; 退出程序mov eax, 60xor edi, edisyscall通过 push 指令将 rax 和 rbx 寄存器的当前值保存到堆栈中该过程被称为保存现场。然后在函数调用结束后我们通过 pop 指令将 rbx 和 rax 寄存器的值从堆栈中恢复这就是恢复现场。mov eax, 60 将系统调用号设置为 60SYS_exit 是 Linux 下的系统调用代表请求操作系统退出程序xor edi, edi 是把 edi 寄存器清零代表退出状态码为 0最后 syscall 指令执行系统调用。
  27. 函数调用的相关命令 call: 此指令用于跳转到函数或子程序。它首先将下一条指令的地址即返回地址压入堆栈然后跳转到目标函数的首地址开始执行。比如call functionName。 ret: 此指令用于从函数或子程序返回。它从堆栈中弹出一个值然后将此值作为即将要执行的下一条指令的地址即跳转回函数调用前的点。如果在 call 之前堆栈有被正确设定ret 就能正常返回。 enter、leave 保护现场和恢复现场的命令
  28. 调用libc函数规范 参数从左到右依次使用RDI、RSI、RDX、RCX、R8、R9寄存器传递参数如果还不够参数从右到左依次入栈返回值RAX或RDX:RAX