scanf一个字符串并在装配气体64位中打印strlen

时间:2017-01-03 21:39:24

标签: string assembly 64-bit scanf gas

我正在尝试使用64位GAS在汇编中编写strlen函数。 我需要从用户那里获取输入字符串并打印 它的长度。这是我的代码:

.lcomm d2, 255
.data
pstring1:  .ascii "%s\0\n"

.text
.globl main
main:
    movq %rsp, %rbp 

    subq $8, %rsp   
    movq  $d2, %rsi
    movq  %rsi,%rbx          
    movq  $pstring1, %rdi
    movq  $0,%rax
    call scanf

    movq   $1, %rax
    movq   $d2, %rsi
    movq   $pstring1, %rdi
    call  printf #print to check if scanf worked write

    add   $8, %rsp

    movq 8(%rsp), %rcx
    movq %rcx, d2
    call pstrlen
    popq %rbx   
    ret

    ##########
pstrlen:  

    movq %rsp, %rbx
    movq 16(%rbp),%rdx
    xor %rax, %rax        
    jmp if

then:
    incq %rax
    movq $length,%rax
if:
    movq %rdx, %rcx
    cmp 0, %rcx
    jne then
end:
    pop %rbp
    ret

如果有人能够解释如何使用字符串并将参数传递给64位GAS汇编中的函数,那将是理想的,因为我找不到任何合适的在线。

1 个答案:

答案 0 :(得分:0)

在原则级别,您使用.lcomm d2, 255为字符串数据分配255个字节。一个字节是8位,1位是0或1.因此当被视为无符号二进制值时,一个字节的最大值是2 8 -1。这对我来说是最常见的方式,我如何考虑字节(作为数字0..255),但这8位也可以表示其他值,例如有时使用带符号的8位(-128..+127),或特定位被寻址,为访问它们的特定代码提供特定功能。 (这部分很好)

然后你使用带有scanf定义的"%s\0\n"(它将编译为字节'%', 's', 0, 10 ...不确定在null终止符之后10对那里有什么好处)。我会使用.asciiz "%254s"代替,以防止恶意用户将更多255字节的输入输入到保留的d2空间。 (请注意.asciiz z的结尾,因此它会在其上添加零字节(

然后使用printf。而是分别为输出提供另一个格式化字符串,这次是formatOut: .asciiz "%s\n"

最后你需要strlen

这意味着我将返回输入。如果你在普通的64b OS(linux)中运行,你的输入字符串很可能是UTF-8编码的(除非你的OS设置在其他特定的Locale中,那么我不确定哪个Locale会scanf选择向上)。

UTF-8编码是可变长度编码,因此您应该决定strlen是否会返回字符数或占用的字节数。

为了简单起见,我假设字节数(不是字符)对你来说已经足够了,如果你的输入字符串只包含基本的7b ASCII字符([0-9A-Za-z !@#$%^&*,.;'\<>?:"|{}]等...请检查任何ASCII表..允许没有重音字符(如á),这将产生多字节UTF8代码),然后字节数也将等于字符数(UTF-8编码与7b ASCII兼容)。

这意味着例如对于输入"Hell 1234",地址d2处的内存将包含这些值(十六进制)48 65 6C 6C 20 31 32 33 34 00。再次,如果您将检查ASCII表,您将意识到例如字节0x20是空格字符等...并且字符串是&#34; nul终止&#34;,最后一个值零是字符串的一部分,但不显示,而是被各种C函数用作&#34;字符串标记的结尾&#34;。

所以你要在strlen中做的是加载一些d2地址的注册表,让我们说rdi。然后逐字节扫描(字节,因为ASCII编码工作在&#34; 1 char = 1字节&#34;方式,我们将忽略UTF-8可变长度代码),直到你在内存中达到零值,并且同时计算它到达它需要多少字节。如果你想对这个想法进行一些思考,那就让它变得简短。对于CPU,您将使用SCASB进行扫描(如果您愿意,还可以使用{#1;手动&#34;使用普通mov/cmp/inc/jne/jnz),您可能会以此结束:

rdi = d2 address
rdx = rdi  ; (copy of d2 address)
ecx = 255  ; maximum length of string
al  = 0    ; value to test against
repne scasb  ; repeat SCASB instruction until zero is found
; here rdi points at the zero byte
; (or it's d2+255 if the zero terminator is missing)
rdi -= rdx ; rdi = length of string
; return result as you wish

因此,您需要首先正确理解您正在操作的值,它们的位置,位/字节大小以及它具有的结构。

然后你可以编写基于这些数据产生任何合理计算的指令。

在您的情况下,计算是&#34; length_of_string =存储在地址d2&#34;的内存中的7b ASCII编码字符串中的非零字节数。 (我的意思是在成功scanf部分代码之后。)

考虑到你的源代码看起来,它看起来像你不了解x86 CPU指令的作用,你只需从一些例子中复制它们。那很快就会让你陷入困境。

例如cmp 0, %rcx正在检查rcx(8字节&#34;宽和#34;值)是否等于零。并且你确实加载了来自rcx的值为rdx的{​​{1}},这是来自堆栈的内容(可能是d2地址),因此rcx将永远不会为零。

即使您实际上将内存中的字符值加载到rcx,您也会同时加载其中的8个,因此您会错过0值,因为它只会是单个一些垃圾中的字节,例如0xCCCCCCCC00343332(例如,0xCC缓冲区后面的未定义内存使用d2,可能有任何值。)

因此代码没有任何意义。如果您至少部分了解什么是CPU寄存器以及mov/inc/cmp/...之类的指令,那么您有机会通过简单地使用调试器来生成工作代码,以验证几乎每1-2个新指令添加到源,如果它确实操纵了正确的值,并修复它们直到你做对了。

这要求您清楚地了解&#34;正确的行为&#34;第一! (就像在这种情况下&#34;从d2地址一个接一个地获取逐字节值,递增&#34;长度&#34;计数器,并寻找零字节)所以你可以告诉我什么时候代码可以满足您的需要。

我想用这个答案指出的是,指令本身虽然重要,但并不像你对数据/结构/算法的看法那么重要。你的问题听起来像你不知道什么是&#34; C string&#34;在x86程序集中,或使用哪种算法。这使你不可能只是猜测&#34;一些指令进入源,然后验证你猜对了。因为你不能说出你想要它做什么。这就是为什么我告诉你应该检查非气体x86组件资源的基础知识,什么是位/字节/计算机内存/等......直到你有点理解操作的数值是什么,例如创建&#34;串&#34;

一旦你知道它应该做什么,你就很容易在调试器中捕获交换参数(例如:movq %rcx, d2 - 为什么要从rcx中放入8个字节在地址d2的内存中?这将覆盖输入字符串),类似的,所以你实际上不需要100%理解指令和气体语法,只需要产生一些东西,然后结束几次迭代到&#34;修复&#34;它。就像检查寄存器+内存视图一样,实现rcx没有改变,而是字符串数据被损坏=&gt;另外尝试一下......

哦,我完全忘记了......你需要找到64b平台ABI的文档,所以你知道将参数传递给C函数的正确方法是什么。

例如在linux中,这些教程可能有所帮助: http://cs.lmu.edu/~ray/notes/gasexamples/

在这里搜索单词&#34; ABI&#34;了解更多资源: https://stackoverflow.com/tags/x86/info