x86 汇编手册快速入门

news2024/11/25 19:31:04

本文翻译自:Guide to x86 Assembly

在阅读 Linux 源码之前,我们需要有一些 x86 汇编知识。本指南描述了 32 位 x86 汇编语言编程的基础知识,包括寄存器结构,数据表示,基本的操作指令(包括数据传送指令、逻辑计算指令、算数运算指令),以及函数的调用规则。

一、寄存器(Registers)

如下图,现代 x86 处理器有 8 个 32-bit 的通用寄存器: 

 

由于历史原因,EAX 寄存器过去被用于算术运算,ECX被用于保存循环索引。尽管现在大多数寄存器在现代指令集中已经失去了它们的特殊用途,但有两个寄存器一直保留用于特殊用途——堆栈指针(ESP,用于指示栈顶位置)和基址指针(EBP,用于指示子程序或函数调用的基址指针)。

对于 EAXEBXECXEDX 四个寄存器来说,其前两个高位字节和后两个低位字节可以独立使用,而后两个低位字节又分为高位(H)和低位(L)部分。这样做的原因主要是为了兼容 16 位程序。

二、内存和寻址模式(Memory and Addressing Modes)

2.1 声明静态数据区(Declaring Static Data Regions)

我们可以使用特殊的汇编指令 .DATA 在 x86 汇编中声明静态数据区域(类似于全局变量),在此指令之后,可以使用指令 DBDW DD分别声明一个、两个和四个字节的数据大小,声明的数据可以用标签(label)标记,以供以后引用。并且按顺序声明的数据将位于内存中相邻的位置

下面是一个相关的例子:

.DATA			
var    DB 64       ; 声明一个字节, 里面的值为 64
var2   DB ?	       ; 声明一个未初始化的字节
       DB 10       ; 声明一个没有 label 的字节, 其值为 10
X	   DW ?	       ; 声明一个未初始化的双字节
Y	   DD 30000    ; 声明一个四字节,值为 30000

x86 汇编语言中的数组只是在内存中连续放置的若干单元格,可以使用 DUP 指令来声明数据数组:


Z     DD 1, 2, 3	; 声明三个四字节的值, 分别初始化为 1, 2, 3; Z+8 位置存放的是 3
bytes DB 10 DUP(?)	; 声明 10 个未初始化的单字节数据
arr	  DD 100 DUP(0) ; 声明 100 个初始化为 0 的四字节数据
str	  DB 'hello',0	; 声明 6 个单字节数据,前五个被初始化为 ASCII 字符, 最后一个初始化为 null(0) 字节

2.2 内存寻址(Addressing Memory)

现代 x86 处理器最多能够寻址  2^{32} 字节大小的内存。在上面的例子中,我们使用标签表示内存区域,这些标签在实际汇编时,均被 32 位的实际地址代替。除了支持这种直接的内存区域描述,X86 还提供了一种灵活的内存寻址方式,即利用最多两个 32 位的寄存器和一个 32 位的有符号常数相加计算一个内存地址,其中一个寄存器可以乘 2, 4 或 8 以表述更大的空间。

下面是正确使用的例子:

mov eax, [ebx]	     ; 将 ebx 值指示的内存地址中的 4 个字节移动到 eax 中
mov [var], ebx	     ; 将 ebx 的内容移动到 var 标签所指示的内存地址中
mov eax, [esi-4]	 ; 将 esi-4 值指示的内存地址中的 4 个字节移动到 eax 中
mov [esi+eax], cl	 ; 将 cl 值的内容移动到 esi+eax 所指示的内存地址中
mov edx, [esi+4*ebx] ; 将 esi+4*ebx 所指示的内存地址中的 4 个字节移动到 edx 中

下面是错误使用的例子:

mov eax, [ebx-ecx]	    ; 只能将寄存器的值相加,不能相减
mov [eax+esi+edi], ebx  ; 最多只能有两个寄存器相加

通常,给定内存地址中数据项的预期大小可以从引用它的汇编代码指令推断出来。当我们加载一个 32 位寄存器时,汇编器可以推断我们引用的内存区域是 4 字节宽。当我们将一个字节寄存器的值存储到内存中时,汇编器可以推断出我们希望地址指向内存中的一个字节。

但对于 mov [ebx], 2 这条指令来说,汇编器无法判断应该将 2 值作为单个字节的数据还是多个字节的数据,这时就需要用到指令 BYTE PTRWORD PTRDWORD PTR,分别表示 1、2 和 4 字节的大小:

mov BYTE PTR [ebx], 2	; 将一个字节表示的 2 移动到 ebx 所指向的内存地址处
mov WORD PTR [ebx], 2	; 将两个字节表示的 2 移动到 ebx 所指向的内存地址处
mov DWORD PTR [ebx], 2  ; 将四个字节表示的 2 移动到 ebx 所指向的内存地址处

三、指令(Instructions) 

机器指令通常分为三类:数据移动指令、算术/逻辑指令和控制流指令。在本节中,我们将分别介绍每种类型的重要 x86 指令示例。 这会使用到如下的几种符号表示:

<reg32>    任意 32 位寄存器 (EAX, EBX, ECX, EDX, ESI, EDI, ESP, or EBP)
<reg16>任意 16 位寄存器 (AX, BX, CX, or DX)
<reg8>任意 8 位寄存器 (AH, BH, CH, DH, AL, BL, CL, or DL)
<reg>任意寄存器
<mem>内存地址 (e.g., [eax], [var + 4], or dword ptr [eax+ebx])
<con32>任意 32 位常数
<con16>任意 16 位常数
<con8>任意 8 位常数
<con>任意 32 位、16 位或 8 位常数

3.1 数据移动指令(Data Movement Instructions)

mov — Move (Opcodes: 88, 89, 8A, 8B, 8C, 8E, ...) 

mov指令将其第二个操作数内容(可以是寄存器、内存或常量)复制到其第一个操作数(寄存器或内存)中。 不能直接用 mov 指令将内存中的值移动到另外一个内存地址。下面是 mov 指令的语法:

mov <reg>,<reg>
mov <reg>,<mem>
mov <mem>,<reg>
mov <reg>,<const>
mov <mem>,<const>

例子:

mov eax, ebx           ; 复制 ebx 中的内容到 eax
mov byte ptr [var], 5  ; 将单字节表示的 5 存入 var 所指示的内存单元

push — Push stack (Opcodes: FF, 89, 8A, 8B, 8C, 8E, ...)

push 指令操作数压入内存的栈中。具体来说,push 指令首先将 ESP (栈指针) 减 4(因为 x86 栈向下增长),然后将其操作数放入地址为 [ESP] 位置的内存单元中。用法如下:

push <reg32>
push <mem>
push <con32>

例子:

push eax — push eax on the stack
push [var] — push the 4 bytes at address var onto the stack

pop — Pop stack

pop 指令从栈中取出一个数据放入指定寄存器或内存中。pop 指令首先移动地址为 [ESP] 位置的内存单元中的数据到指定寄存器或内存,然后再将 ESP 加 4。用法如下:

pop <reg32>
pop <mem>

例子:

pop edi — pop the top element of the stack into EDI.
pop [ebx] — pop the top element of the stack into memory at the four bytes starting at location EBX.

lea — Load effective address

lea 指令将第二个操作数指定的内存地址放置到第一个操作数指定的寄存器中。该指令不会加载该内存位置的内容,只计算有效地址并放入寄存器。用法如下:

lea <reg32>,<mem>

例子:

lea edi, [ebx+4*esi] — the quantity EBX+4*ESI is placed in EDI.
lea eax, [var] — the value in var is placed in EAX.
lea eax, [val] — the value val is placed in EAX.

3.2 算术/逻辑指令(Arithmetic and Logic Instructions)

add — Integer Addition

add 指令将两个操作数相加,结果存储到第一个操作数中。用法如下:

add <reg>,<reg>
add <reg>,<mem>
add <mem>,<reg>
add <reg>,<con>
add <mem>,<con>

例子:

add eax, 10 — EAX ← EAX + 10
add BYTE PTR [var], 10 — add 10 to the single byte stored at memory address var

sub — Integer Subtraction

sub 指令将两个操作数相减,结果存储到第一个操作数中。用法如下:

sub <reg>,<reg>
sub <reg>,<mem>
sub <mem>,<reg>
sub <reg>,<con>
sub <mem>,<con>

例子:

sub al, ah — AL ← AL - AH
sub eax, 216 — subtract 216 from the value stored in EAX

inc, dec — Increment, Decrement

inc 指令将操作数的内容加一,而 dec 指令将操作数的内容减一。用法如下:

inc <reg>
inc <mem>
dec <reg>
dec <mem>

例子:

dec eax — subtract one from the contents of EAX.
inc DWORD PTR [var] — add one to the 32-bit integer stored at location var

imul — Integer Multiplication

imul 指令分为两个操作数和三个操作数两种类型,如果是两个操作数,则让两个操作数相乘,并将结果存入第一个操作数中;如果是三个操作数,则让第二个和第三个操作数相乘,结果存入第一个操作数中,且第三个操作数必须是常数。不管哪种类型,第一个操作数必须是寄存器。用法如下:

imul <reg32>,<reg32>
imul <reg32>,<mem>
imul <reg32>,<reg32>,<con>
imul <reg32>,<mem>,<con>

例子:

imul eax, [var] — multiply the contents of EAX by the 32-bit contents of the memory location var. Store the result in EAX.
imul esi, edi, 25 — ESI → EDI * 25

idiv — Integer Division

idiv 指令将 64 位整数 EDX:EAX (将 EDX 视为高 4 字节,将 EAX 视为低 4 字节) 的内容除以指定的操作数值。除法的商存储在 EAX 中,余数存储在 EDX 中。 用法如下:

idiv ebx — divide the contents of EDX:EAX by the contents of EBX. Place the quotient in EAX and the remainder in EDX.
idiv DWORD PTR [var] — divide the contents of EDX:EAX by the 32-bit value stored at memory location var. Place the quotient in EAX and the remainder in EDX.

and, or, xor — Bitwise logical and, or and exclusive or

and,or,xor 指令分别对两个操作数作与、或、异或操作,结果存入第一个操作数中。用法如下:

and <reg>,<reg>
and <reg>,<mem>
and <mem>,<reg>
and <reg>,<con>
and <mem>,<con>

or <reg>,<reg>
or <reg>,<mem>
or <mem>,<reg>
or <reg>,<con>
or <mem>,<con>

xor <reg>,<reg>
xor <reg>,<mem>
xor <mem>,<reg>
xor <reg>,<con>
xor <mem>,<con>

例子:

and eax, 0fH — clear all but the last 4 bits of EAX.
xor edx, edx — set the contents of EDX to zero.

not — Bitwise Logical Not

not 指令逻辑上对操作数的内容求反 (即翻转操作数中的所有位)。用法如下:

not <reg>
not <mem>

neg — Negate

neg 指令对操作数内容取负。用法如下:

neg <reg>
neg <mem>

Example
neg eax — EAX → - EAX

shl, shr — Shift Left, Shift Right

shl, shr 指令会左右移动第一个操作数的位,用0填充空的位。操作数最多可以被移动 31 位。移位的位数由第二个操作数指定,若操作数大于 32,则对其求模。 用法如下:

shl <reg>,<con8>
shl <mem>,<con8>
shl <reg>,<cl>
shl <mem>,<cl>

shr <reg>,<con8>
shr <mem>,<con8>
shr <reg>,<cl>
shr <mem>,<cl>

3.3 控制流指令(Control Flow Instructions)

x86 处理器维持着一个指示当前执行指令的指令指针(IP),当一条指令执行后,此指针自动指向下一条指令。IP 寄存器不能直接操作,但是可以用控制流指令更新。

一般用标签(label)指示程序中的地址,在 x86 汇编代码中,可以在任何指令前加入标签。如:

       mov esi, [ebp+8]
begin: xor ecx, ecx
       mov eax, [esi]

这种标签只是用于取代 32 位地址值的一种便利的表示方式。这样在其他代码中就可以使用该标签,而不是使用其对应具体的 32 位地址。

jmp — Jump

jmp 指令用于跳转到指定的 label 位置处执行。用法如下:

jmp <label>

Example
jmp begin — Jump to the instruction labeled begin.

jcondition — Conditional Jump

jcondition 类的指令用于条件跳转(即满足条件才跳转)。这些条件存储在一个称为机器状态字 (machine status word) 的特殊寄存器中。一般会在执行下列指令之前先执行 cmp 指令对两个操作数进行比较。用法如下:

je <label> (jump when equal)
jne <label> (jump when not equal)
jz <label> (jump when last result was zero)
jg <label> (jump when greater than)
jge <label> (jump when greater than or equal to)
jl <label> (jump when less than)
jle <label> (jump when less than or equal to)

Example
cmp eax, ebx
jle done

; 如果 eax 小于 ebx 的内容,则跳转到标签 done 所指向的位置处指向,若大于则继续往下执行

cmp — Compare

cmp 指令比较两个操作数指定的值,然后设置适当的机器状态字的状态码。这条指令等同于减法指令,但是会舍弃减法的结果。用法如下:

cmp <reg>,<reg>
cmp <reg>,<mem>
cmp <mem>,<reg>
cmp <reg>,<con>

Example
cmp DWORD PTR [var], 10
jeq loop

callret — Subroutine call and return

call 和 ret 指令用于实现子程序的调用和返回。

call 指令首先将当前代码执行位置压入栈中,然后无条件跳转到 label 操作数指定的位置。与简单的跳转指令不同,该指令会保存当前代码执行位置,以便子程序执行完后返回。

ret 指令实现子程序的返回。该指令首先从栈弹出 call 指令保存的代码执行位置,然后无条件返回到该位置继续执行。

call <label>
ret

四、调用约定(Calling Convention)

为了加强程序员之间的协作及简化程序开发进程,设定一个函数调用约定非常必要,函数调用约定规定了函数调用及返回的规则,只要遵照这种规则写的程序均可以正确执行,从而程序员不必关心诸如参数如何传递等问题;另一方面,在汇编语言中可以调用符合这种规则的高级语言所写的函数,从而将汇编语言程序与高级语言程序有机结合在一起。有了这个约定,我们就可以在汇编代码中调用 C 函数,或者在 C 代码中调用汇编实现的函数。

调用约定分为两个方面,及调用者约定和被调用者约定,如一个函数 A 调用一个函数 B,则 A 被称为调用者 (Caller),B 被称为被调用者 (Callee)。

下图显示一个调用过程中的内存中的栈布局: 

 

4.1 Caller Rules

调用者规则包括一系列约定:

  1. 在调用子程序之前,调用者应该保存一系列被设计为调用者应该保存的寄存器的值。调用者应该保存的寄存器有 EAX、ECX、EDX。由于被调用的子程序允许修改这些寄存器,如果调用者在子程序返回后还需要用到这些寄存器的值,则调用者必须将这些寄存器中的值压入堆栈(以便在子例程返回后可以恢复它们)。 
  2. 要将参数传递给子例程,需要在调用之前将它们压入栈。参数应该按倒序推送 (即最后一个参数先 push),因为栈是向下增长的。
  3. 使用 call 指令调用子程序。该指令将返回地址放在栈的顶部,并转到子程序执行(子程序的执行将按照被调用者的规则执行)。

在子例程返回之后,调用者可以期望在寄存器 EAX 中找到子程序的返回值。为了恢复调用子程序执行之前的状态,调用者应该执行以下操作:

  1. 清除栈中的参数。
  2. 将栈中保存的 EAX 值、ECX 值以及 EDX 值出栈,恢复 EAX、ECX、EDX 的值(当然,如果其它寄存器在调用之前需要保存,也需要完成类似入栈和出栈操作)。

例子:

push [var] ; Push last parameter first
push 216   ; Push the second parameter
push eax   ; Push first parameter last

call _myFunc ; Call the function (assume C naming)

add esp, 12

上述例子展示了一个调用者执行的操作,在调用 _myFunc 函数之前,先保存函数的三个传入参数 eax、216 和 var,然后使用 call 指令调用函数,返回后再将 esp(栈指针)的值加 12(三个参数,每个四字节),加 12 的目的是为了清空栈中的三个参数。

_myFunc 的返回值保存在 eax 中。若该函数会改变 ecx 和 edx 中的值,调用者还必须在调用之前将其也保存在栈中,并在调用结束之后,出栈恢复ecx和edx的值。

4.2 Callee Rules

  1. 将 EBP(基址寄存器)入栈(push ebp),并将 ESP 中的值拷贝到 EBP 中(mov ebp esp),目的是保存调用子程序之前的基址指针,基址指针用于寻找栈上的参数和局部变量。当一个子程序开始执行时,基址指针保存栈指针指示子程序的执行。为了在子程序完成之后调用者能正确定位调用者的参数和局部变量,ebp的值需要返回。
  2. 在栈上为局部变量分配空间。
  3. 保存 callee-saved 寄存器的值,callee-saved 寄存器包括 ebx,edi 和 esi,将 ebx,edi 和 esi 压栈。

在上述三个步骤完成之后,子程序开始执行,当子程序返回时,必须完成如下工作:

  1. 将返回的执行结果保存在 eax 中
  2. 弹出栈中保存的 callee-saved 寄存器值,恢复 callee-saved 寄存器的值(ESI和EDI)
  3. 收回局部变量的内存空间。实际处理时,通过改变 EBP 的值即可:mov esp, ebp。 
  4. 通过弹出栈中保存的 ebp 值恢复调用者的基址寄存器值。
  5. 执行 ret 指令返回到调用者程序。

例子:

.486
.MODEL FLAT
.CODE
PUBLIC _myFunc
_myFunc PROC
  ; Subroutine Prologue
  push ebp     ; Save the old base pointer value.
  mov ebp, esp ; Set the new base pointer value.
  sub esp, 4   ; Make room for one 4-byte local variable.
  push edi     ; Save the values of registers that the function
  push esi     ; will modify. This function uses EDI and ESI.
  ; (no need to save EBX, EBP, or ESP)

  ; Subroutine Body
  mov eax, [ebp+8]   ; Move value of parameter 1 into EAX
  mov esi, [ebp+12]  ; Move value of parameter 2 into ESI
  mov edi, [ebp+16]  ; Move value of parameter 3 into EDI

  mov [ebp-4], edi   ; Move EDI into the local variable
  add [ebp-4], esi   ; Add ESI into the local variable
  add eax, [ebp-4]   ; Add the contents of the local variable
                     ; into EAX (final result)

  ; Subroutine Epilogue 
  pop esi      ; Recover register values
  pop  edi
  mov esp, ebp ; Deallocate local variables
  pop ebp ; Restore the caller's base pointer value
  ret
_myFunc ENDP
END

子程序首先通过入栈的手段保存 ebp,分配局部变量,保存寄存器的值。

在子程序体中,参数和局部变量均是通过 ebp 进行计算。由于参数传递在子程序被调用之前,所以参数总是在 ebp 指示的地址的下方(在栈中),因此,上例中的第一个参数的地址是 ebp+8,第二个参数的地址是 ebp+12,第三个参数的地址是 ebp+16;而局部变量在 ebp 指示的地址的上方,所有第一个局部变量的地址是 ebp-4,而第二个这是 ebp-8.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/949346.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

42、Flink 的table api与sql之Hive Catalog

Flink 系列文章 1、Flink 部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接 13、Flink 的table api与sql的基本概念、通用api介绍及入门示例 14、Flink 的table api与sql之数据类型: 内置数据类型以及它们的属性 15、Flink 的ta…

匠心新品:大彩科技超薄7寸WIFI线控器发布,热泵、温控器、智能家电首选!

一、产品介绍 此次发布一款7寸高清全新外壳产品&#xff0c;让HMI人机界面家族再添一新成员。该产品相比其他外壳有以下5个大改动&#xff1a; 1 表面玻璃盖板使用2.5D立体结构&#xff1b; 2 液晶盖板采用一体黑设计&#xff0c;且液晶屏与触摸板是全贴合结构&#xff1b; …

leetcode 84. 柱状图中最大的矩形

2023.8.30 本题和接雨水 有点类似&#xff0c;依旧用双指针来解。但是本题要记录的是当前柱子 左右两侧第一个小于该柱子的索引。将其保存在两个数组中&#xff0c;最后再求最大面积。代码如下&#xff1a; class Solution { public:int largestRectangleArea(vector<int&g…

图表背后的故事:数据可视化的威力与影响

数据可视化现在在市场上重不重要&#xff1f;这已经不再是一个简单的问题&#xff0c;而是一个不可忽视的现实。随着信息时代的来临&#xff0c;数据已经成为企业和组织的核心资产&#xff0c;而数据可视化则成为释放数据价值的重要工具。 在当今竞争激烈的商业环境中&#xf…

css换行

强制显示一行&#xff0c;超出... .box{white-space: nowrap; /* 强制显示一行 */overflow: hidden;text-overflow: ellipsis; /* 超出... */ } 自动换行 一般默认制动换行 .box1{word-wrap:break-word; } 显示2行&#xff0c;超出... .box2 {overflow: hidden;display: -…

SpringBoot初级开发--服务请求(GET/POST)所有参数的记录管理(8)

服务端在定位错误的时候&#xff0c;有时候要还原现场&#xff0c;这就要把当时的所有入参参数都能记录下来&#xff0c;GET还好说&#xff0c;基本NGINX都会记录。但是POST的请求参数基本不会被记录&#xff0c;这就需要我们通过一些小技巧来记录这些参数&#xff0c;放入日志…

Mybatis1.5 单个条件(动态SQL)

1.5 单个条件&#xff08;动态SQL&#xff09; 1.5.1 编写接口方法1.5.2 编写SQL语句1.5.3 编写测试方法 如上图所示&#xff0c;在查询时只能选择 品牌名称、当前状态、企业名称 这三个条件中的一个&#xff0c;但是用户到底选择哪儿一个&#xff0c;我们并不能确定。这种就属…

【小吉测评】哔哩哔哩接入AI?!效果如何?

文章目录 &#x1f384;前言⭐申请方式&#x1f3f3;️‍&#x1f308;注意 &#x1f6f8;简介&#x1f354;上手体验&#x1f6f8;进行数学计算&#x1f970;可以写代码吗 &#x1f384;前言 最近人工智能特别火&#xff0c;chatgpt&#xff0c;Claude2&#xff0c;文心一言等…

怎么把图片放大并且清晰?有详细的方法步骤

怎么把图片放大并且清晰&#xff1f;数字图像处理中的图片放大是许多行业和领域中广泛应用的一项技术。常规的放大方法通过插值或复制像素的方式增加像素数&#xff0c;但这会导致失真和模糊。无损放大是一种特殊的放大方法&#xff0c;它可以通过数学算法来增加图片的尺寸&…

从Gamma空间改为Linear空间会导致性能下降吗

1&#xff09;从Gamma空间改为Linear空间会导致性能下降吗 2&#xff09;如何处理没有使用Unity Ads却收到了GooglePlay平台的警告 3&#xff09;C#端如何处理xLua在执行DoString时候死循环 4&#xff09;Texture2DArray相关 这是第350篇UWA技术知识分享的推送&#xff0c;精选…

Transformer升级之路:逆用Leaky ReRoPE

©PaperWeekly 原创 作者 | 苏剑林 单位 | 科学空间 研究方向 | NLP、神经网络 在《Transformer升级之路&#xff1a;无限外推的ReRoPE&#xff1f;》中&#xff0c;笔者提出了 ReRoPE 和 Leaky ReRoPE&#xff0c;诸多实验结果表明&#xff0c;它们能够在几乎不损失训练效…

【设计模式】装饰者模式

目录 一、定义二、结构三、优点四、使用场景五、代码示例六、截图示例 一、定义 1.在不改变现有对象结构的情况下&#xff0c;动态给该对象添加额外功能的模式 2.类B继承于类A&#xff0c;并将类A作为B类的属性&#xff08;B类聚合A类&#xff09; 3.BufferedInputStream、Buff…

单元测试用例mock的使用方法

单元测试用例mock的使用方法 提升代码测试覆盖率的关键策略 为什么单元测试是如此重要&#xff1f; 在软件开发中&#xff0c;单元测试是一个关键的环节&#xff0c;可以确保代码的质量和稳定性。而在进行单元测试时&#xff0c;使用mock对象可以帮助我们更好地测试代码逻辑…

MySQL8.Xx安装控制台未生成随机密码解决方案

MySQL8.xx一主两从复制安装与配置 MySQL8.XX随未生成随机密码解决方案 MySQL8.0.30一主两从复制与配置(一) 一: Mysql 安装时控制台未生成密码 安装过程中解压或者安装时报错等,这种情况一般是因网络等其他原因导致下载的安装包不完整&#xff0c; 重新下载安装即可; 二:…

域名解析与http服务器实现原理

域名解析函数gethostbyname struct hostent *gethostbyname(const char *name); 主机结构在<netdb.h>中定义如下&#xff1a; 结构的成员包括&#xff1a; h_name&#xff1a;主机的正式名称 h_aliases&#xff1a;主机的备用名称数组&#xff0c;以NULL结尾指针 h_…

企业为什么要做自动化测试?我们该如何学习自动化测试?

作为一名大厂测试开发&#xff0c;编写自动化脚本&#xff0c;俨然成为一种必备的技能。 为什么要做自动化测试&#xff1f; 那企业或者是我们测试人员为什么要做自动化测试呢&#xff1f; 如果在前两年&#xff0c;可能10个测试员有6个都是做的功能测试&#xff0c;但随着测…

ToBeWritten之防御规避战术

也许每个人出生的时候都以为这世界都是为他一个人而存在的&#xff0c;当他发现自己错的时候&#xff0c;他便开始长大 少走了弯路&#xff0c;也就错过了风景&#xff0c;无论如何&#xff0c;感谢经历 转移发布平台通知&#xff1a;将不再在CSDN博客发布新文章&#xff0c;敬…

day-05 TCP半关闭 ----- DNS ----- 套接字的选项

一、优雅的断开套接字连接 之前套接字的断开都是单方面的。 &#xff08;一&#xff09;基于TCP的半关闭 Linux的close函数和windows的closesocket函数意味着完全断开连接。完全断开不仅不能发送数据&#xff0c;从而也不能接收数据。在某些情况下&#xff0c;通信双方的某一方…

2023高教社杯数学建模思路 - 案例:异常检测

文章目录 赛题思路一、简介 -- 关于异常检测异常检测监督学习 二、异常检测算法2. 箱线图分析3. 基于距离/密度4. 基于划分思想 建模资料 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 一、简介 – 关于异常…

众创空间、孵化器、共享办公这三者之间的异同点

众创空间、孵化器和共享办公都是创新型创业生态中重要的组成部分&#xff0c;但它们在服务方式、功能和作用上有所区别。 众创空间是一种开放式的创新型创业生态&#xff0c;旨在通过提供共享办公空间、创业培训、融资对接等服务&#xff0c;帮助初创企业加速成长。众创空间注…