HLA程序问题:字符串以'A'结尾检测异常及内存访问错误排查
HLA字符串结尾检测程序问题排查
问题描述
编写的HLA程序用于检测字符串是否以'A'(含小写'a')结尾,存在两个问题:
- 输入以'A'结尾的字符串(如
sfrA)时,无法输出预期的after endsWithA --- result= 1 - 输入非'A'结尾的字符串(如
gtf)时,触发内存访问违规错误
当前程序代码
program StringProgram; #include( "stdlib.hhf" ); #include( "cs17string.hla" ); // allows use of gets and puts static stringData : dword; answer : int32; procedure endsWithA( stringData : dword ); @nodisplay; @noframe; static dReturnAddress : dword; begin endsWithA; // Preserve registers push( EAX ); push( EBX ); push( ECX ); push( EDX ); // Get the return address off the stack pop( dReturnAddress ); // Get stringData off the stack mov( stringData, EAX ); // Calculate the length of the string mov( EAX, EBX ); mov( EAX, ECX ); // Check if the string is empty cmp( ECX, 0 ); je no_end_with_a; // Get the last character dec( ECX ); add( EBX, ECX ); mov( [EBX], AL ); // Compare the last character with 'A' and 'a' cmp( AL, 'A' ); je end_with_a; cmp( AL, 'a' ); je end_with_a; no_end_with_a: mov( 0, EAX ); jmp done; end_with_a: mov( 1, EAX ); done: // Restore the registers used pop( EDX ); pop( ECX ); pop( EBX ); pop( EAX ); // Push back the return address push( dReturnAddress ); // Return from function ret(); end endsWithA; begin StringProgram; stdout.put( "Please enter a string to process", nl ); // This code allocates a string of size 80 mov( @size( int8 ), AL ); mov( 80, BL ); inc( BL ); mul( BL ); mov( 0, EBX ); mov( AX, BX ); malloc( EBX ); mov( EAX, stringData ); // Let's try reading a value into the string mov( stringData, EAX ); push( EAX ); mov( 80, CX ); push( CX ); call gets; // Print the string stdout.put( "----> here is the string you entered: " ); mov( stringData, EAX ); push( EAX ); call puts; stdout.newln(); // Initialize EAX before calling the function mov( 0, EAX ); // Pass the string parameter to the function mov( stringData, EAX ); call endsWithA; mov( EAX, answer ); // Show the results stdout.put( "after endsWithA --- result=" ); stdout.put( answer ); stdout.newln(); end StringProgram;
当前输出示例
Please enter a string to process sfrA ----> here is the string you entered: sfrA Please enter a string to process gtf ----> here is the string you entered: gtf
预期输出示例
Please enter a string to process sfrA ----> here is the string you entered: sfrA after endsWithA --- result= 1
问题分析与修复
核心错误点
- 函数调用参数传递错误:
程序定义的endsWithA函数要求通过栈传递dword参数,但调用时未将字符串指针压入栈,而是用EAX传递,导致函数无法获取正确的输入地址。 - 栈处理逻辑完全混乱:
进入函数后先push寄存器,再尝试pop返回地址,此时pop的是自己push的EDX值(而非真正的返回地址),直接破坏栈结构,导致返回地址丢失、寄存器恢复失效。 - 字符串长度计算错误:
将字符串指针直接赋值给ECX,把内存地址当成字符串长度,导致空字符串判断错误,后续计算最后一个字符的地址时出现严重偏移,触发内存访问违规。
修复后的完整代码
program StringProgram; #include( "stdlib.hhf" ); #include( "cs17string.hla" ); // allows use of gets and puts static stringData : dword; answer : int32; procedure endsWithA( stringData : dword ); @nodisplay; @noframe; static dReturnAddress : dword; begin endsWithA; // 先获取返回地址(栈顶是call指令压入的返回地址) pop( dReturnAddress ); // 获取函数参数(返回地址下方是调用时push的参数) pop( EAX ); // 保存寄存器现场 push( EBX ); push( ECX ); push( EDX ); mov( EAX, EBX ); // EBX保存字符串指针 mov( 0, ECX ); // 手动计算字符串长度(遍历到0终止符) len_calc: cmp( [EBX + ECX], byte ptr 0 ); je len_done; inc( ECX ); jmp len_calc; len_done: // 判断空字符串 cmp( ECX, 0 ); je no_end_with_a; // 获取最后一个字符 dec( ECX ); mov( [EBX + ECX], AL ); // 匹配'A'或'a' cmp( AL, 'A' ); je end_with_a; cmp( AL, 'a' ); je end_with_a; no_end_with_a: mov( 0, EAX ); jmp done; end_with_a: mov( 1, EAX ); done: // 恢复寄存器 pop( EDX ); pop( ECX ); pop( EBX ); // 把返回地址放回栈,用ret(4)自动平衡栈(清除push的4字节参数) push( dReturnAddress ); ret(4); end endsWithA; begin StringProgram; stdout.put( "Please enter a string to process", nl ); // 分配81字节(80字符+终止符) malloc( 81 ); mov( EAX, stringData ); // 读取输入字符串 push( stringData ); push( 80 ); call gets; // 打印输入的字符串 stdout.put( "----> here is the string you entered: " ); push( stringData ); call puts; stdout.newln(); // 调用检测函数(正确传递参数:push字符串指针) push( stringData ); call endsWithA; mov( EAX, answer ); // 输出结果 stdout.put( "after endsWithA --- result= " ); stdout.put( answer ); stdout.newln(); // 释放内存(避免泄漏) push( stringData ); call free; end StringProgram;
关键修复说明
- 修正参数传递与栈处理:
- 调用
endsWithA前先push字符串指针,符合HLA栈传递参数的约定 - 函数内先pop返回地址,再pop参数,保证获取正确的输入指针
- 使用
ret(4)自动平衡栈(清除push的4字节参数),替代手动调整栈指针
- 调用
- 修复字符串长度计算:
通过循环遍历字符串直到遇到0终止符,正确计算长度,避免将指针地址当作长度使用 - 优化内存分配:
直接用malloc(81)简化分配逻辑,同时添加free释放内存,避免内存泄漏 - 调整寄存器保存顺序:
仅保存实际使用的寄存器(EBX、ECX、EDX),减少栈操作,恢复顺序与push顺序严格遵循后进先出原则
内容的提问来源于stack exchange,提问作者Tiburcio
相关产品推荐
相关产品推荐

