asmでスマートに文字列の長さを算出する

文字列の長さを算出

以下のasmコード(x64)で、文字列の長さを取得することができる。

mov      eax, 0x0
mov      rcx, 0xffffffffffffffff
repnz    scas al, BYTE PTR es:[rdi]
not      rcx 
lea      rcx, [rcx - 1] // dec rcxでも可

x86がいい人は適宜読みかえて欲しい。

解説

repnz/repneはcmps、 scas命令に付加されるプリフィックスで、(r,e)cxレジスタをカウンタとして使用し、カウンタが0になるか ZFフラグが1になるまで繰り返すというものである。repeat not zeroと読めば覚えやすい。上のコードではscas命令に付加されている。

scas命令はオペランドで指定された文字をサイズに応じてal、ax、eax、raxレジスタと比較し、結果によってEFLAGSレジスタを変更するというものである。scan stringと読めば覚えやすい。上のコードではmov eax, 0x0によってalが0(終端文字)となっているので文字が終端文字がどうかを確認している。

これでrepnzプレフィックスとscas命令によって文字列がどこで終わるかをカウンタによって数えていると分かる。rcxレジスタに0xffffffffffffffffという大きい値が入っているのは、文字列の長さより小さいカウンタによってループが終了しないようにするためである。

not命令でカウンタの減算分をrcxにいれているのだが、ここが少しわかりにくい。各bitが立っている状態から減算していくと、結果の各bitを反転させると減算分を求めることができる。例えば、0xff(1111 1111)から2を引くと0xfd(1111 1101)となる。これを反転させると0x2(0000 0010)となり減算分が分かる。これを利用してカウンタの減算分を算出している。

最後に、rep系の命令はrcxのデクリメント後にEFLAGSレジスタをチェックするので、rcxレジスタをlea rcx, [rcx - 1] もしくは、dec rcxによってデクリメントする必要がある。インクリメントではないのはrcxにカウンタの減算分が入っているからだ。デクリメントすることで文字列の長さがrcxレジスタに格納された。

まとめ

もっと愚直に書くこともできるが、repnzプレフィックスとscas命令、not命令によってスマートに文字列の長さを求められる。reversing中に上記のコードが出てきたときはさくっと読んでいきたい。

しゃろさんに指摘をもらって修正した。ありがとうございました!!