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

专业系统网站品牌推广策划方案案例

专业系统网站,品牌推广策划方案案例,国家建设工程网,做摘抄的网站过程的实现离不开堆栈的应用#xff0c;堆栈是一种后进先出(LIFO)的数据结构#xff0c;最后压入栈的值总是最先被弹出#xff0c;而新数值在执行压栈时总是被压入到栈的最顶端#xff0c;栈主要功能是暂时存放数据和地址#xff0c;通常用来保护断点和现场。 栈是由CPU管…过程的实现离不开堆栈的应用堆栈是一种后进先出(LIFO)的数据结构最后压入栈的值总是最先被弹出而新数值在执行压栈时总是被压入到栈的最顶端栈主要功能是暂时存放数据和地址通常用来保护断点和现场。 栈是由CPU管理的线性内存数组,它使用两个寄存器(SS和ESP)来保存栈的状态SS寄存器存放段选择符而ESP寄存器的值通常是指向特定位置的一个32位偏移值我们很少需要直接操作ESP寄存器相反的ESP寄存器总是由CALL,RET,PUSH,POP等这类指令间接性的修改。 CPU提供了两个特殊的寄存器用于标识位于系统栈顶端的栈帧。 ESP 栈指针寄存器栈指针寄存器其内存放着一个指针该指针永远指向系统栈最上面一个栈帧的栈顶。EBP 基址指针寄存器基址指针寄存器其内存放着一个指针该指针永远指向系统栈最上面一个栈帧的底部。 在通常情况下ESP是可变的随着栈的生成而逐渐变小而EBP寄存器是固定的只有当函数的调用后发生入栈操作而改变。 执行PUSH压栈时堆栈指针自动减4再将压栈的值复制到堆栈指针所指向的内存地址。执行POP出栈时从栈顶移走一个值并将其复制给内存或寄存器然后再将堆栈指针自动加4。执行CALL调用时CPU会用堆栈保存当前被调用过程的返回地址直到遇到RET指令再将其弹出。 10.1 PUSH/POP PUSH和POP是汇编语言中用于堆栈操作的指令它们通常用于保存和恢复寄存器的值参数传递和函数调用等。 PUSH指令用于将操作数压入堆栈中它执行的操作包括将操作数复制到堆栈的栈顶并将堆栈指针ESP减去相应的字节数。指令格式如下 PUSH operand其中operand可以是8位16位或32位的寄存器立即数以及内存中的某个值。例如要将寄存器EAX的值压入堆栈中可以使用以下指令 PUSH EAX从汇编代码的角度来看PUSH指令将操作数存储到堆栈中它实际上是一个入栈操作。 POP指令用于将堆栈中栈顶的值弹出到指定的目的操作数中它执行的操作包括将堆栈顶部的值移动到指定的操作数并将堆栈指针增加相应的字节数。指令格式如下 POP operand其中operand可以是8位16位或32位的寄存器立即数以及内存中的某个位置。例如要将从堆栈中弹出的值存储到BX寄存器中可以使用以下指令 POP EBX从汇编代码的角度来看POP指令将从堆栈中取出一个值并将其存储到目的操作数中它是一个出栈操作。 在函数调用时PUSH指令被用于向堆栈中推送函数的参数这些参数可以是寄存器、立即数或者内存中的某个值。在函数返回之前POP指令被用于将堆栈顶部的值弹出并将其存储到寄存器或者内存中。 读者需要特别注意在使用PUSH和POP指令时需要保证堆栈的平衡也就是说每个PUSH指令必须有对应的POP指令否则堆栈会失去平衡最终导致程序出现错误。 在读者了解了这两条指令时则可以执行一些特殊的操作如下代码我们以数组入栈与出栈为例执行PUSH指令时首先减小ESP的值然后把源操作数复制到堆栈上执行POP指令则是先将数据弹出到目的操作数中然后再执行ESP值增加4并以此分别将数组中的元素压入栈最终再通过POP将元素反弹出来。 .386p.model flat,stdcalloption casemap:noneinclude windows.inc include kernel32.inc includelib kernel32.lib include msvcrt.inc includelib msvcrt.lib.dataArray DWORD 1,2,3,4,5,6,7,8,9,10szFmt BYTE %d ,0dh,0ah,0 .codemain PROC; 使用Push指令将数组正向入栈mov eax,0mov ecx,10S1:push dword ptr ds:[Array eax * 4]inc eaxloop S1; 使用pop指令将数组反向弹出mov ecx,10S2:push ecx ; 保护ecxpop ebx ; 将Array数组元素弹出到ebxinvoke crt_printf,addr szFmt,ebxpop ecx ; 弹出ecxloop S2int 3main ENDP END main至此当读者理解了这两个指令之后那么利用堆栈的先进后出特定我们就可以实现将特殊的字符串反转后输出的效果首先我们循环将字符串压入堆栈然后再从堆栈中反向弹出来这样就可以实现字符串的反转操作这段代码的实现也相对较为容易 .386p.model flat,stdcalloption casemap:noneinclude windows.inc include kernel32.inc includelib kernel32.lib include msvcrt.inc includelib msvcrt.lib.dataMyString BYTE hello lyshark,0NameSize DWORD ($ - MyString) - 1szFmt BYTE %s,0dh,0ah,0 .codemain PROC; 正向压入字符串mov ecx,dword ptr ds:[NameSize]mov esi,0S1: movzx eax,byte ptr ds:[MyString esi]push eaxinc esiloop S1; 反向弹出字符串mov ecx,dword ptr ds:[NameSize]mov esi,0S2: pop eaxmov byte ptr ds:[MyString esi],alinc esiloop S2invoke crt_printf,addr szFmt,addr MyStringint 3main ENDP END main10.2 PROC/ENDP PROC/ENDP 伪指令是用于定义过程函数的伪指令这两个伪指令可分别定义过程的开始和结束位置。此处读者需要注意这两条伪指令并非是汇编语言中所兼容的而是MASM编译器为我们提供的一个宏是MASM的一部分它允许程序员使用汇编语言定义过程函数可以像标准汇编指令一样使用。 对于不使用宏定义来创建函数时我们通常会自己管理函数栈参数而有了宏定义这些功能都可交给编译器去管理下面的一个案例中我们通过使用过程创建ArraySum函数实现对整数数组求和操作函数默认将返回值存储在EAX中并打印输出求和后的参数。 .386p.model flat,stdcalloption casemap:noneinclude windows.inc include kernel32.inc includelib kernel32.lib include msvcrt.inc includelib msvcrt.lib.dataMyArray DWORD 1,2,3,4,5,6,7,8,9,10Sum DWORD ?szFmt BYTE %d,0dh,0ah,0 .code; 数组求和过程ArraySum PROCpush esi ; 保存ESI,ECXpush ecxxor eax,eaxS1: add eax,dword ptr ds:[esi] ; 取值并相加add esi,4 ; 递增数组指针loop S1pop ecx ; 恢复ESI,ECXpop esiretArraySum endpmain PROClea esi,dword ptr ds:[MyArray] ; 取出数组基址mov ecx,lengthof MyArray ; 取出元素数目call ArraySum ; 调用方法mov dword ptr ds:[Sum],eax ; 得到结果invoke crt_printf,addr szFmt,Sumint 3main ENDP END main接着我们来实现一个具有获取随机数功能的案例在C语言中如果需要获得一个随机数一般会调用Seed函数如果读者逆向分析过这个函数的实现原理那么读者应该能理解在调用取随机数之前会生成一个随机数种子这个随机数种子的生成则依赖于0x343FDh这个特殊的常量地址当我们每次访问该地址都会产出一个随机的数据当得到该数据后我们再通过除法运算取出溢出数据作为随机数使用实现了该功能。 .386p.model flat,stdcalloption casemap:noneinclude windows.inc include kernel32.inc includelib kernel32.lib include msvcrt.inc includelib msvcrt.lib.dataseed DWORD 1szFmt BYTE 随机数: %d,0dh,0ah,0 .code; 生成 0 - FFFFFFFFh 的随机种子Random32 PROCpush edxmov eax, 343FDhimul seedadd eax, 269EC3hmov seed, eaxror eax,8pop edxretRandom32 endp; 生成随机数RandomRange PROCpush ebxpush edxmov ebx,eaxcall Random32mov edx,0div ebxmov eax,edxpop edxpop ebxretRandomRange endpmain PROC; 调用后取出随机数call RandomRangeinvoke crt_printf,addr szFmt,eaxint 3main ENDP END main10.3 局部参数传递 在汇编语言中可以使用堆栈来传递函数参数和创建局部变量。当程序执行到函数调用语句时需要将函数参数传递给被调用函数。为了实现参数传递程序会将参数压入栈中然后调用被调用函数。被调用函数从栈中弹出参数并执行然后将返回值存储在寄存器中最后通过跳转返回到调用函数。 局部变量也可以通过在栈中分配内存来创建。在函数开始时可以使用push指令将局部变量压入栈中。在函数结束时可以使用pop指令将变量从栈中弹出。由于栈是后进先出的数据结构局部变量的创建可以很方便地通过在栈上压入一些数据来实现。 局部变量是在程序运行时由系统动态的在栈上开辟的在内存中通常在基址指针(EBP)之下尽管在汇编时不能给定默认值但可以在运行时初始化如下一段C语言伪代码: void MySub() {int var1 10;int var2 20; }上述的代码经过C编译后会变成如下汇编指令其中EBP-4必须是4的倍数因为默认就是4字节存储如果去掉了mov esp,ebp那么当执行pop ebp时将会得到EBP等于10执行RET指令会导致控制转移到内存地址10处执行从而程序会崩溃。 MySub PROCpush ebp ; 将EBP存储在栈中mov ebp,esp ; 堆栈框架的基址sub esp,8 ; 创建局部变量空间(分配2个局部变量)mov DWORD PTR [ebp-8],10 ; var1 10mov DWORD PTR [ebp-4],20 ; var2 20mov esp,ebp ; 从堆栈上删除局部变量pop ebp ; 恢复EBP指针ret 8 ; 返回,清理堆栈 MySub ENDP为了使上述代码片段更易于理解可以在上述的代码的基础上给每个变量的引用地址都定义一个符号并在代码中使用这些符号如下代码所示代码中定义了一个名为MySub的过程该过程将两个局部变量分别设置为10和20。 在该过程中首先使用push ebp指令将旧的基址指针压入栈中并将ESP寄存器的值存储到ebp中。这个旧的基址指针将在函数执行完毕后被恢复。然后我们使用sub esp,8指令将8字节的空间分配给两个局部变量。在堆栈上分配的空间可以通过var1_local和var2_local符号来访问。在这里我们定义了两个符号将它们与ebp寄存器进行偏移以访问这些局部变量。var1_local的地址为[ebp-8]var2_local的地址为[ebp-4]。然后我们使用mov指令将10和 20分别存储到这些局部变量中。最后我们将ESP寄存器的值存储回ebp中并使用pop ebp指令将旧的基址指针弹出堆栈。现在栈顶指针(ESP)下移恢复上面分配的8个字节的空间最后通过ret 8返回到调用函数。 在使用堆栈传参和创建局部变量时需要谨慎考虑栈指针的位置并确保遵守调用约定以确保正确地传递参数和返回值。 var1_local EQU DWORD PTR [ebp-8] ; 添加符号1 var2_local EQU DWORD PTR [ebp-4] ; 添加符号2MySub PROCpush ebpmov ebp,espsub esp,8mov var1_local,10mov var2_local,20mov esp,ebppop ebpret 8 MySub ENDP接着我们来实现一个具有功能的案例首先为了能更好的让读者理解我们先使用C语言方式实现MakeArray()函数该函数的内部是动态生成的一个MyString数组并通过循环填充为星号字符串最后使用POP弹出并输出结果观察后尝试用汇编实现。 void makeArray() {char MyString[30];for(int i0;i30;i){myString[i] *;} }call makeArray()上述C语言代码如果翻译为汇编格式则如下所示代码使用汇编语言实现makeArray的程序该程序开辟了一个长度为30的数组将其中的元素填充为*然后弹出两个元素并将它们输出到控制台。 .386p.model flat,stdcalloption casemap:noneinclude windows.inc include kernel32.inc includelib kernel32.lib include msvcrt.inc includelib msvcrt.lib.dataszFmt BYTE 出栈数据: %x ,0dh,0ah,0 .codemakeArray PROCpush ebpmov ebp,esp; 开辟局部数组sub esp,32 ; MyString基地址位于 [ebp - 30]lea esi,[ebp - 30] ; 加载MyString的地址; 填充数据mov ecx,30 ; 循环计数S1: mov byte ptr ds:[esi],* ; 填充为*inc esi ; 每次递增一个字节loop S1; 弹出2个元素并输出,出栈数据pop eaxinvoke crt_printf,addr szFmt,eaxpop eaxinvoke crt_printf,addr szFmt,eax ; 以下平栈,由于我们手动弹出了2个数据; 则平栈 32 - (2 * 4) 24 add esp,24 ; 平栈mov esp,ebppop ebp ; 恢复EBPretmakeArray endpmain PROCcall makeArrayinvoke ExitProcess,0main ENDP END main在该程序的开始部分我们首先通过push ebp和mov ebp,esp指令保存旧的基址指针并将当前栈顶指针(ESP)存储到ebp中。然后我们使用sub esp, 32指令开辟一个长度为30的数组MyString。我们将MyString数组的基地址存储在[ebp - 30]的位置。使用lea esi, [ebp - 30]指令将MyString的基地址加载到esi寄存器中。该指令偏移ebp-30是因为ebp-4是MakeArray函数的第一个参数的位置因此需要增加四个字节。我们利用MOV byte ptr ds:[esi],*指令将MyString中的所有元素填充为*。 然后使用pop eax和invoke crt_printf, addr szFmt, eax指令两次弹出两个元素并使用crt_printf函数输出这些元素。该函数在msvcrt.dll库中实现用于将格式化的信息输出到控制台。在输出数据之后我们通过add esp,24和mov esp,ebp指令将堆栈平衡恢复旧的基址指针ebp然后从堆栈中弹出ebp并通过ret指令返回到调用程序。 接着我们继续来对比一下堆栈中参数传递的异同点平栈的方式一般可分为调用者平栈和被调用者平栈在使用堆栈传参时需要平衡栈以恢复之前的堆栈指针位置。 当平栈由被调用者完成时被调用函数使用ret指令将控制权返回到调用函数并从堆栈中弹出返回地址。此时被调用函数需要将之前分配的局部变量从堆栈中弹出以便调用函数能够恢复堆栈指针的位置。因此被调用函数必须知道其在堆栈上分配的内存大小并将该大小与其ret指令中的参数相匹配以便调用函数可以正确恢复堆栈指针位置。 当平栈由调用者完成时调用函数需要在调用子函数之前平衡堆栈。因此调用函数需要知道子函数在堆栈上分配的内存大小并在调用子函数之前向堆栈提交额外的空间。调用函数可以使用add esp, N指令来恢复堆栈指针的位置其中 N 是被调用函数在堆栈上分配的内存大小。然后调用函数调用被调用函数该函数将返回并将堆栈指针恢复到调用函数之前的位置。 如下这段汇编代码中笔者分别实现了两种调用方式其中MyProcA函数是一种被调用者平栈由于调用者并没有堆栈修正所以需要在函数内部通过使用ret 12的方式平栈之所以是12是因为我们使用了三个局部变量而第二个MyProcB函数则是调用者平栈该方式在函数内部并没有返回任何参数所以在调用函数结束后需要通过add esp,4的方式对堆栈进行修正。 .386p.model flat,stdcalloption casemap:noneinclude windows.inc include kernel32.inc includelib kernel32.lib include msvcrt.inc includelib msvcrt.lib.dataszFmt BYTE 数据: %d ,0dh,0ah,0 .code; 第一种方式被调用者平栈MyProcA PROCpush ebpmov ebp,espxor eax,eaxmov eax,dword ptr ss:[ebp 16] ; 获取第一个参数mov ebx,dword ptr ss:[ebp 12] ; 获取第二个参数mov ecx,dword ptr ss:[ebp 8] ; 获取第三个参数add eax,ebxadd eax,ebxadd eax,ecxmov esp,ebppop ebpret 12 ; 此处ret12可平栈,也可使用 add ebp,12MyProcA endp; 第二种方式调用者平栈MyProcB PROCpush ebpmov ebp,espmov eax,dword ptr ss:[ebp 8]add eax,10mov esp,ebppop ebpretMyProcB endpmain PROC; 第一种被调用者MyProcA平栈 3*4 12push 1push 2push 3call MyProcAinvoke crt_printf,addr szFmt,eax; 第二种方式调用者平栈push 10call MyProcBadd esp,4invoke crt_printf,addr szFmt,eaxint 3main ENDP END main当然了如果读者认为自己维护堆栈很繁琐则此时可以直接使用MASM汇编器提供的PROC定义过程使用该伪指令汇编器会自行计算所需要使用的变量数量并自行在结尾处添加对应的平栈语句这段代码实现起来将变得非常容易理解。 .386p.model flat,stdcalloption casemap:noneinclude windows.inc include kernel32.inc includelib kernel32.lib include msvcrt.inc includelib msvcrt.lib.dataszFmt BYTE 计算参数: %d ,0dh,0ah,0.codemy_proc PROC x:DWORD,y:DWORD,z:DWORD ; 定义过程局部参数LOCAL sum:DWORD ; 定义局部变量存放总和mov eax,dword ptr ds:[x]mov ebx,dword ptr ds:[y] ; 分别获取到局部参数mov ecx,dword ptr ds:[z]add eax,ebxadd eax,ecx ; 相加后放入eaxmov sum,eaxretmy_proc endpmain PROCLOCAL ret_sum:DWORDpush 10push 20push 30 ; 传递参数call my_procmov ret_sum,eax ; 获取结果并打印invoke crt_printf,addr szFmt,ret_sumint 3main ENDP END main这里笔者还需要扩展一个伪指令LOCALLOCAL是一种汇编语言中的伪指令用于定义存储在堆栈上的局部变量。使用LOCAL指令定义的局部变量只在函数执行时存在当函数返回后该变量将被删除。根据使用LOCAL指令时指定的内存空间大小汇编器将为每个变量保留足够的空间。 例如下面是一个使用LOCAL定义局部变量的示例 .386p.model flat,stdcalloption casemap:noneinclude windows.inc include kernel32.inc includelib kernel32.lib.codemain PROC; 定义局部变量,自动压栈/平栈LOCAL var_byte:BYTE,var_word:WORD,var_dword:DWORDLOCAL var_array[3]:DWORD; 填充局部变量mov byte ptr ds:[var_byte],1mov word ptr ds:[var_word],2mov dword ptr ds:[var_dword],3; 填充数组方式1lea esi,dword ptr ds:[var_array]mov dword ptr ds:[esi],10mov dword ptr ds:[esi 4],20mov dword ptr ds:[esi 8],30; 填充数组方式2mov var_array[0],100mov var_array[1],200mov var_array[2],300invoke ExitProcess,0main ENDP END main在上述示例代码中main过程使用LOCAL指令定义了几个局部变量包括一个字节类型的变量var_byte、一个字类型的变量var_word、一个双字类型的变量var_dword和一个包含三个双字元素的数组var_array。 在代码中我们使用mov指令填充这些变量的值。对于字节类型、字类型和双字类型的变量使用mov byte ptr ds:[var_byte], 1、mov word ptr ds:[var_word], 2和mov dword ptr ds:[var_dword], 3指令将相应的常数值存储到变量中。在填充数组时分别使用了两种不同的方式。一种方式是使用lea指令将数组的地址加载到esi寄存器中然后使用mov dword ptr ds:[esi],10等指令将相应的常数值存储到数组中。另一种方式是直接访问数组元素如mov var_array[0], 100等指令。需要注意由于数组元素在内存中是连续存储的因此可以使用[]操作符访问数组元素。 在汇编中使用LOCAL伪指令来实现自动计算局部变量空间以及最后的平栈操作将会极大的提高开发效率。 10.4 USES/ENTER USES是汇编语言中的伪指令用于保存一组寄存器的状态以便函数调用过程中可以使用这些寄存器。使用USES时程序可以保存一组需要保护的寄存器汇编器将在程序入口处自动向堆栈压入这些寄存器的值。读者需注意我们可以在需要保存寄存器的程序段中使用USES来保护寄存器但不应在整个程序中重复使用寄存器。 ENTER也是一种伪指令用于创建函数调用过程中的堆栈帧。使用ENTER时程序可以定义一个名为ENTER的指定大小的堆栈帧。该指令会将新的基准指针ebp 压入堆栈同时将当前的基准指针ebp存储到另一个寄存器ebx中然后将堆栈指针esp减去指定大小的值获取新的基地址并将新的基地址存储到ebp 中。之后程序可以在此帧上创建和访问局部变量并使用LEAVE指令将堆栈帧删除将ebp恢复为旧的值同时将堆栈指针平衡。 在使用USES和ENTER指令时需要了解这些指令在具体的平台上的支持情况以及它们适用的调用约定。通常情况下在函数开头我们将使用ENTER创建堆栈帧然后使用USES指定需要保护的寄存器。在函数末尾我们使用LEAVE删除堆栈帧。 .386p.model flat,stdcalloption casemap:noneinclude windows.inc include kernel32.inc includelib kernel32.lib.code; USES 自动压入 eax,ebx,ecx,edxmy_proc PROC USES eax ebx ecx edx x:DWORD,y:DWORDenter 8,0 ; 自动保留8字节堆栈空间add eax,ebxleavemy_proc endpmain PROCmov eax,10mov ebx,20call my_procint 3main ENDP END main10.5 STRUCT/UNION STRUCT和UNION是汇编语言中的数据类型STRUCT是一种复合数据类型它将多个不同类型的变量按顺序放置在一起并使用单个名称来引用集合。使用STRUCT时我们可以将不同类型的变量组合成一个结构体并定义其属性如结构体中包含的成员变量的数据类型、名称和位置。 例如下面是一个使用STRUCT定义自定义类型的示例 ; 定义一个名为 MyStruct 的结构体包含两个成员变量。 MyStruct STRUCTVar1 DWORD ?Var2 WORD ? MyStruct ENDS在上述示例代码中我们使用STRUCT定义了一个名为MyStruct 的结构体其中包含两个成员变量Var1和Var2。其中Var1是DWORD类型的数据类型以问号?形式指定了其默认值Var2是WORD类型的数据类型。 另一个数据类型是UNION它也是一种复合数据类型用于将多个不同类型的变量叠加在同一内存位置上。使用UNION时程序内存中的数据将只占用所有成员变量中最大的数据类型变量的大小。与结构体不同联合中的所有成员变量共享相同的内存位置。我们可以使用一种成员变量来引用内存位置但在任何时候仅能有一种成员变量存储在该内存位置中。 例如下面是一个使用UNION定义自定义类型的示例 ; 定义一个名为 MyUnion 的联合包含两个成员变量。 MyUnion UNIONVar1 DWORD ?Var2 WORD ? MyUnion ENDS在上述示例代码中我们使用UNION定义了一个名为MyUnion的联合其中包含两个不同类型的成员变量Var1和Var2将它们相对应地置于联合的同一内存位置上。 读者在使用STRUCT和UNION时需要根据内存分布和变量类型来正确访问成员变量的值。在汇编语言中结构体和联合主要用于定义自定义数据类型、通信协议和系统数据结构等如下一段代码则是汇编语言中实现结构体赋值与取值的总结。 .386p.model flat,stdcalloption casemap:noneinclude windows.inc include kernel32.inc includelib kernel32.lib; 定义坐标结构 MyPoint Structpos_x DWORD ?pos_y DWORD ?pos_z DWORD ? MyPoint ends; 定义人物结构 MyPerson StructFname db 20 dup(0)fAge db 100fSex db 20 MyPerson ends.data; 声明结构: 使用 ,{}符号均可PtrA MyPoint 10,20,30PtrB MyPoint {100,200,300}; 声明结构: 使用MyPerson声明结构UserA MyPerson lyshark,24,1.codemain PROC; 获取结构中的数据lea esi,dword ptr ds:[PtrA]mov eax,(MyPoint ptr ds:[esi]).pos_xmov ebx,(MyPoint ptr ds:[esi]).pos_ymov ecx,(MyPoint ptr ds:[esi]).pos_z; 向结构中写入数据lea esi,dword ptr ds:[PtrB]mov (MyPoint ptr ds:[esi]).pos_x,10mov (MyPoint ptr ds:[esi]).pos_y,20mov (MyPoint ptr ds:[esi]).pos_z,30; 直接获取结构中的数据mov eax,dword ptr ds:[UserA.Fname]mov ebx,dword ptr ds:[UserA.fAge]int 3main ENDP END main接着我们来实现一个输出结构体数组的功能结构数组其实就是一维的空间因此使用两个比例因子即可实现寻址操作如下代码我们先来实现一个简单的功能只遍历第一层结构数组外层的数据。 .386p.model flat,stdcalloption casemap:noneinclude windows.inc include kernel32.inc includelib kernel32.lib include msvcrt.inc includelib msvcrt.lib; 定义坐标结构 MyPoint Structpos_x DWORD ?pos_y DWORD ?pos_z DWORD ? MyPoint ends; 定义循环结构 MyCount Structcount_x DWORD ?count_y DWORD ? MyCount ends.data; 声明结构: 使用 ,{}符号均可PtrA MyPoint 10,20,30,40,50,60,70,80,90,100,110,120Count MyCount 0,0szFmt BYTE 结构数据: %d,0dh,0ah,0.codemain PROC; 获取结构中的数据lea esi,dword ptr ds:[PtrA]mov eax,(MyPoint ptr ds:[esi]).pos_x ; 获取第一个结构Xmov eax,(MyPoint ptr ds:[esi 12]).pos_x ; 获取第二个结构X; while 循环输出结构的每个首元素元素mov (MyCount ptr ds:[Count]).count_x,0S1: cmp (MyCount ptr ds:[Count]).count_x,48 ; 12 * 4 48jge lop_endmov ecx,(MyCount ptr ds:[Count]).count_xmov eax,dword ptr ds:[PtrA ecx] ; 寻找首元素invoke crt_printf,addr szFmt,eaxmov eax,(MyCount ptr ds:[Count]).count_xadd eax,12 ; 每次递增12mov (MyCount ptr ds:[Count]).count_x,eaxjmp S1lop_end:int 3main ENDP END main接着我们递增难度通过每次递增将两者的偏移相加获得比例因子通过因子嵌套双层循环实现寻址打印。 .386p.model flat,stdcalloption casemap:noneinclude windows.inc include kernel32.inc includelib kernel32.lib include msvcrt.inc includelib msvcrt.lib; 定义坐标结构 MyPoint Structpos_x DWORD ?pos_y DWORD ?pos_z DWORD ? MyPoint ends; 定义循环结构 MyCount Structcount_x DWORD ?count_y DWORD ? MyCount ends.data; 声明结构: 使用 ,{}符号均可PtrA MyPoint 10,20,30,40,50,60,70,80,90,100,110,120Count MyCount 0,0szFmt BYTE 结构数据: %d,0dh,0ah,0.codemain PROC; 获取结构中的数据lea esi,dword ptr ds:[PtrA]mov eax,(MyPoint ptr ds:[esi]).pos_x ; 获取第一个结构Xmov eax,(MyPoint ptr ds:[esi 12]).pos_x ; 获取第二个结构X; while 循环输出结构的每个首元素元素mov (MyCount ptr ds:[Count]).count_x,0S1: cmp (MyCount ptr ds:[Count]).count_x,48 ; 12 * 4 48jge lop_endmov (MyCount ptr ds:[Count]).count_y,0S3: cmp (MyCount ptr ds:[Count]).count_y,12 ; 3 * 4 12jge S2mov eax,(MyCount ptr ds:[Count]).count_xadd eax,(MyCount ptr ds:[Count]).count_y ; 相加得到比例因子mov eax,dword ptr ds:[PtrA eax] ; 使用相对变址寻址invoke crt_printf,addr szFmt,eaxmov eax,(MyCount ptr ds:[Count]).count_yadd eax,4 ; 每次递增4mov (MyCount ptr ds:[Count]).count_y,eaxjmp S3 S2: mov eax,(MyCount ptr ds:[Count]).count_xadd eax,12 ; 每次递增12mov (MyCount ptr ds:[Count]).count_x,eaxjmp S1lop_end:int 3main ENDP END main结构体同样支持内嵌的方式,如下Rect指针中内嵌两个MyPoint分别指向左子域和右子域,这里顺便定义一个MyUnion联合体把,其使用规范与结构体完全一致,只不过联合体只能存储一个数据. .386p.model flat,stdcalloption casemap:noneinclude windows.inc include kernel32.inc includelib kernel32.lib; 定义坐标结构 MyPoint Structpos_x DWORD ?pos_y DWORD ?pos_z DWORD ? MyPoint ends; 定义左右结构 Rect StructLeft MyPoint Right MyPoint Rect ends; 定义联合体 MyUnion Unionmy_dword DWORD ?my_word WORD ?my_byte BYTE ? MyUnion ends.dataPointA Rect PointB Rect {10,20,30,100,200,300}test_union MyUnion {1122h}szFmt BYTE 结构数据: %d,0dh,0ah,0 .codemain PROC; 嵌套结构的赋值mov dword ptr ds:[PointA.Left.pos_x],100mov dword ptr ds:[PointA.Left.pos_y],200mov dword ptr ds:[PointA.Right.pos_x],100mov dword ptr ds:[PointA.Right.pos_y],200; 通过地址定位lea esi,dword ptr ds:[PointB]mov eax,dword ptr ds:[PointB] ; 定位第一个MyPointmov eax,dword ptr ds:[PointB 12] ; 定位第二个内嵌MyPoint; 联合体的使用mov eax,dword ptr ds:[test_union.my_dword]mov ax,word ptr ds:[test_union.my_word]mov al,byte ptr ds:[test_union.my_byte]main ENDP END main当然有了结构体这一成员的加入我们同样可以在汇编层面实现链表的定义与输出如下代码所示首先定义一个ListNode用于存储链表结构的数据域与指针域接着使用TotalNodeCount定义链表节点数量最后使用REPEAT伪指令开辟ListNode对象的多个实例其中的NodeData域包含一个1-15的数据后面的($ Counter * sizeof ListNode)则是指向下一个链表的头指针通过不断遍历则可输出整个链表。 .386p.model flat,stdcalloption casemap:noneinclude windows.inc include kernel32.inc includelib kernel32.lib include msvcrt.inc includelib msvcrt.libListNode StructNodeData DWORD ?NextPtr DWORD ? ListNode endsTotalNodeCount 15 Counter 0.dataLinkList LABEL PTR ListNodeREPEAT TotalNodeCountCounter Counter 1ListNode Counter,($ Counter * sizeof ListNode)ENDMListNode0,0 ; 标志着结构链表的结束szFmt BYTE 结构地址: %x 结构数据: %d,0dh,0ah,0 .codemain PROCmov esi,offset LinkList; 判断下一个节点是否为0,0L1: mov eax,(ListNode PTR [esi]).NextPtrcmp eax,0je lop_end; 显示节点数据mov eax,(ListNode PTR [esi]).NodeDatainvoke crt_printf,addr szFmt,esi,eax; 获取到下一个节点的指针mov esi,(ListNode PTR [esi]).NextPtrjmp L1lop_end:int 3main ENDP END main本文作者 王瑞 本文链接 https://www.lyshark.com/post/e43f6d19.html 版权声明 本博客所有文章除特别声明外均采用 BY-NC-SA 许可协议。转载请注明出处
http://www.hkea.cn/news/14520718/

相关文章:

  • 互联网网站建设方案重庆铜梁网站建设费用
  • 武隆网站建设哪家好企业网站模板中文
  • 深圳公明网站建设企业免费建站软件
  • 吉林省网站制作公司有哪些禹城市建设局网站
  • 如何了解和掌握一个网站的权重医院网站建设论证报告
  • 阿里巴巴做特卖的网站盛泽网站建设
  • 怎么建立网站模版网站大图做多大尺寸
  • dw怎么做网站标题图标网站首页添加浮动飘窗
  • 霸屏seo服务亚马逊seo什么意思
  • 报送举报网站建设情况免费网站安全软件下载安装
  • 网络公司网站设计方案ppt铁岭网站开发公司
  • 建设网站 请示 报告wordpress 微信登录界面
  • 极速网站建设哪家好网站建设捌金手指下拉一
  • 建设工程中标通知书查询网站贵州省赤水市代码
  • 大型php网站系统莱州人社局网站
  • 创办网站需要多少钱网络推广员的前景
  • 做简单网站需要学什么软件有哪些国外的智慧城市建设网站
  • 企企业业网网站站建建设设阿里企业网站建设评估
  • 网站免费的有没有网站上职业学校排名 该怎么做
  • 聊城wap网站建设产品推广方案ppt
  • 网站地图 seo漳州正规网站建设费用
  • 网站后台添加内容网页不显示河南郑州做网站的公司
  • 网站建设工作总结6有哪些网站可以做青旅义工
  • 安徽省建设厅质量监督站网站天津建设工程信息网公布
  • 广州万户网站公司河南省建设执业资格中心网站
  • 晋州做网站的联系电话公众平台网站开发哪家好
  • 如何让做的网站自动适应浏览器做网站需要哪些知识
  • 盛世阳光-网站建设小程序推广怎么赚钱
  • 企业服务网站制作天津做网络推广公司有哪些
  • 太原网站建设找山西云起时网站建设需要使用哪些设备