You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题分析与修复

核心错误点

  1. 函数调用参数传递错误:
    程序定义的endsWithA函数要求通过栈传递dword参数,但调用时未将字符串指针压入栈,而是用EAX传递,导致函数无法获取正确的输入地址。
  2. 栈处理逻辑完全混乱:
    进入函数后先push寄存器,再尝试pop返回地址,此时pop的是自己push的EDX值(而非真正的返回地址),直接破坏栈结构,导致返回地址丢失、寄存器恢复失效。
  3. 字符串长度计算错误:
    将字符串指针直接赋值给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;

关键修复说明

  1. 修正参数传递与栈处理:
    • 调用endsWithA前先push字符串指针,符合HLA栈传递参数的约定
    • 函数内先pop返回地址,再pop参数,保证获取正确的输入指针
    • 使用ret(4)自动平衡栈(清除push的4字节参数),替代手动调整栈指针
  2. 修复字符串长度计算:
    通过循环遍历字符串直到遇到0终止符,正确计算长度,避免将指针地址当作长度使用
  3. 优化内存分配:
    直接用malloc(81)简化分配逻辑,同时添加free释放内存,避免内存泄漏
  4. 调整寄存器保存顺序:
    仅保存实际使用的寄存器(EBX、ECX、EDX),减少栈操作,恢复顺序与push顺序严格遵循后进先出原则

内容的提问来源于stack exchange,提问作者Tiburcio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 04:33:10