VOOZH about

URL: https://qiita.com/tobira-code/items/ac3169200160566c35af

⇱ x86-64プロセッサでGNU assemblerを使う #Linux - Qiita


👁 Image
44

Go to list of users who liked

34

Share on X(Twitter)

Share on Facebook

Add to Hatena Bookmark

More than 5 years have passed since last update.

@tobira-code

x86-64プロセッサでGNU assemblerを使う

44
Last updated at Posted at 2017-03-24

はじめに

x86-64プロセッサでAssembly Codeを作成する方法を説明します。

Assembly CodeをMachine Codeに変換するプログラムをAssemblerといいます。
ここではGNU assembler(GAS)を使用します。

Assembly Codeには決まり事があります。
この決まり事はApplication binary interface(ABI) or Calling conventionと呼ばれており、
instruction set / OS / compilerによって変わります。

x86-64プロセッサの代表的なABIは次の通りです。
それぞれUnix like OS / Windowsに対応します。

  • System V AMD64 ABI
  • Microsoft x64 calling convention

ここではSystem V AMD64 ABIについて説明します。

cpu / os / gcc

使用したcpu / os / gccを明記します。

  • CPU Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz
  • OS ubuntu-14.04-desktop-amd64
  • gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2

basic information

data type, register, instructionを説明します。

  • data type
  • general purpose register
  • instructions

次の資料を参考にしました。

[1] System V Application Binary Interface AMD64 Architecture Processor Supplement Draft Version 0.3
[2]Computer Systems - A Programmers Perspective
[3]x86-64 Machine-Level Programming Randal E. Bryant David R. O’Hallaron September 9, 2005
[4]Notes on x86-64 programming

data type

x86-64 registers, memory and operationsは次に示すdata typesを使用します。
次の表はC declaration / Intel data type / GAS suffix / x86-64 Sizeの対応表です。

C declaration Intel data type GAS suffix x86-64 Size (Bytes)
char Byte b 1
short Word w 2
int Double word l 4
unsigned Double word l 4
long int Quad word q 8
unsigned long Quad word q 8
char * Quad word q 8
float Single precision s 4
double Double precision d 8
long double Extended precision t 16

Long / Pointerは64bitです。LP64として知られています。
なお、Microsoft x64 calling convention は
Long Long / Pointerが64bitです。LLP64として知られています。

GAS suffixとはGNU assemblerのニーモニックの後ろに付く文字です。
mov命令を例にすると、
movbはオペランドが1byteであることを、
movqはオペランドが8byteであることを示します。

なお、GAS suffixは省略できます。具体的には
movl %eax, %ebx と記載しなくても、単にmov %eax, %ebxでよいです。

general purpose register

16個のgeneral purpose registers(GPRs)が利用できます。サイズは64bit長です。

63 31 15 7 0 bit
 -------------------------------------------------------
 | rax | eax ax | ah | al |
 -------------------------------------------------------
 | rbx | ebx bx | bh | bl |
 -------------------------------------------------------
 | rcx | ecx cx | ch | cl |
 -------------------------------------------------------
 | rdx | edx dx | dh | dl |
 -------------------------------------------------------
 | rsi | esi si | | sil |
 -------------------------------------------------------
 | rdi | edi di | | dil |
 -------------------------------------------------------
 | rbp | ebp bp | | bpl |
 -------------------------------------------------------
 | rsp | esp sp | | spl |
 -------------------------------------------------------
 | r8 | r8d r8w | | r8b |
 -------------------------------------------------------
 | r9 | r9d r9w | | r9b |
 -------------------------------------------------------
 | r10 | r10d r10w | | r10b |
 -------------------------------------------------------
 | r11 | r11d r11w | | r11b |
 -------------------------------------------------------
 | r12 | r12d r12w | | r12b |
 -------------------------------------------------------
 | r13 | r13d r13w | | r13b |
 -------------------------------------------------------
 | r14 | r14d r14w | | r14b |
 -------------------------------------------------------
 | r15 | r15d r15w | | r15b |
 -------------------------------------------------------

同じregisterを32bit / 16bit / 8bit長でアクセスできます。
raxを例に説明します。名称と対応するbitは以下の通りです。
raxは64bit
eaxは下位32bit
axは下位16bit
ahは下位16bitの上位8bit
alは下位16bitの下位8bit

各registerは次に示すように用途が決められています。

  • Return value : rax
  • Argument : rdi(1st) rsi(2nd) rdx(3rd) rcx(4th) r8(5th) r9(6th)
  • Callee saved : rbx rbp r13-15
  • Stack pointer : rsp

(*)資料[1]には
r10はtemporary register, used for passing a function’s static chain pointer
r11はtemporary register
r12はcallee-saved register
と記載されています。

Callee saved registerは呼出し先で変更できないregisterです。
呼出し元の視点ではcall命令の前後で値が変わらないことが保証されます。

呼出し先でCallee saved registerを利用する場合は
registerの値をmemoryに保存・復元する必要があります。

次のregisterは関数の引数に利用されます。

Arg # Size (bits) > > >
64 32 16 8
1 %rdi %edi %di %dil
2 %rsi %esi %si %sil
3 %rdx %edx %dx %dl
4 %rcx %ecx %cx %cl
5 %r8 %r8d %r8w %r8b
6 %r9 %r9d %r9w %r9b

必要に応じてcall命令の前にregisterに値を設定します。

例えばadd(int a, int b)という関数をadd(10, 20)のように呼び出す場合、
次のAssembly Codeになります。%esi(1st)に10を%edi(2nd)に20を設定してcallします。

test.s
 .global main
format:
 .asciz "%d\n"
main:
 mov $10, %esi
 mov $20, %edi
 call add
 mov $format, %rdi
 mov %eax, %esi
 xor %rax, %rax
 call printf
 ret
add:
 xor %rax, %rax
 add %edi, %eax
 add %esi, %eax
 ret

instructions

x86-64 Instruction setを説明します。
以降の説明で用いる用語を説明します。

  • Iは直値です。
  • Rはregisterです。
  • Sは直値 or register or memoryです。
  • Dはregister or memoryです。
  • Mはmemoryです。
  • (*1)のInstructionには後ろに[b|w|l|q]が付きます。
  • (*2)のInstructionには後ろに[bw|bl|bq|wl|wq|lq]が付きます。

(*1)の例としてmov(*1)の場合、
movb / movw / movl / movqとなります。b,w,l,qはGAS suffixです。

(*2)の例としてmovs(*2)の場合、
movsbw / movsbl / movsbq / movswl / movswq / movslqとなります。
bwの意味は b → wの変換をするという意味です。bl/bq/wl/wq/lqも同様の意味です。

Data movement

register / memory間でデータをコピーする命令を説明します。
Instructionの一覧を示します。

Instruction Effect Description
mov(*1) S, D S → D Move
movabsq I, R I → R Move quad word
movs(*2) S, R SignExtend(S) → R Move sign-extended
movz(*2) S, R ZeroExtend(S) → R Move zero-extended
pushq S R[%rsp]-8 → R[%rsp];
S → M[R[%rsp]]
Push
popq D M[R[%rsp]] → D;
R[%rsp]+8 → R[%rsp]
Pop

movは同一サイズのデータをコピーする命令です。DにSをコピーします。
movabsqは64bitの直値を64bit registerにコピーします。
movsはsign extension, movzはzero extensionします。詳細は後で説明します。
pushq / popqはstackに8byteデータをpush / popします。

(*)movzlq命令はありません。理由は[3]に次のように記載されています。

Perhaps unexpectedly, instructions that move or generate
32-bit register values also set the upper 32 bits of the register to zero.
Consequently there is no need for an instruction movzlq.

sign extension / zero extension

データをサイズの大きい領域にコピーする場合、次の2つの方式があります。

  • sign extension
  • zero extension

sign extensionは

  • コピー元の最上位ビットが1であればコピー先の上位ビットに1を埋める、
  • コピー元の最上位ビットが0であればコピー先の上位ビットに0を埋める、

方式です。

zero extensionはコピー先の上位ビットに0を埋める方式です。

sign extensionでは符号が変化しません。
zero extensionでは符号が変化します。
扱う整数が符号付きかどうかで使い分けます。

例.1byte を 2byteに代入するケース

case 1: 0010 1101 (signed +0x2d, unsigned 0x2d)
 0010 1101 b -> 0000 0000 0010 1101 b (sign ext; +0x002d)
 0010 1101 b -> 0000 0000 0010 1101 b (zero ext; 0x002d)

case 2: 1010 1101 (signed -0x53, unsigned 0xad)
 1010 1101 b -> 1111 1111 1010 1101 b (sign ext; -0x0053)
 1010 1101 b -> 0000 0000 1010 1101 b (zero ext; 0x00ad)

Addressing Modes

mov命令はmemoryを参照できます。memory addressは次の形式で参照します。
effective addressといいます。

offset(base,index,scale)

effective addressは(base + index * scale + offset)で計算します。

offsetは固定値 or labelです。base / indexはregisterです。
scaleは1 or 2 or 4 or 8です。

例. %rsp

# %rspに設定されたaddressにあるwordを%diにコピーします。
# offset / index / scaleは省略されています。
mov (%rsp), %di

# mov %rsp, %di
# ()なしにすると%rspの内容をコピーすることになります。誤りです。

PC-relative addressing

x86-64でコンパイルした実行ファイルをobjdumpでdisassembleすると
%ripからの相対アドレスで表現している命令が多くあることに気づきます。
%ripを使ったAddressingをPC-relative addressingといいます。

例.

$ objdump -d test | grep rip
 4003e4:	48 8b 05 0d 0c 20 00 	mov 0x200c0d(%rip),%rax # 600ff8 <_DYNAMIC+0x1d0>
 400400:	ff 35 02 0c 20 00 	pushq 0x200c02(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
(...)
$ 

この形式が使われる理由は次の通りです。

Offsets are limited to 32 bits. This means that only a 4GB window into the
potential 64-bit address space can be accessed from a given base value. This is
mainly an issue when accessing static global data. It is standard to access this
data using PC-relative addressing (using %rip as the base). For example,
we would write the address of a global value stored at location labeled a as
a(%rip), meaning that the assembler and linker should cooperate to compute the
offset of a from the ultimate location of the current instruction.

Integer arithmetic & logic operations

Integerのarithmetic & logic operationsを説明します。

  • lea
  • arithmetic : inc / dec / add / sub
  • logic : neg / not / xor / or / and
  • shift : sal / sar / shl / shr

次のInstructionはすべて(*1)となります。

Instruction Effect Description
lea S, D &S → D Load effective address
inc D D+1 → D Increment
dec D D-1 → D Decrement
add S, D D+S → D Add
sub S, D D-S → D Subtract
neg D -D → D Negate
not D ~D →D Complement
xor S, D D^S → D Exclusive-or
or S, D D|S → D Or
and S, D D&S → D And
sal k, D D<<k → D Left shift
shl k, D D<<k → D Left shift (same as sal)
sar k, D D>>k → D Arithmetic right shift
shr k, D D>>k → D Logical right shift

lea / sarについて補足説明します。
lea はeffective addressをDに設定します。

例. lea と movの違い
movは%ripが指し示すaddressの先の8byteを%rsiにコピーします。
leaは%ripが指し示すaddressを%rsiにコピーします。

lea (%rip), %rsi # 0x400542
mov (%rip), %rsi # 0xfffffebfe8c03148

sarは最上位ビットを保持したまま右bit shiftします。
つまり
最上位ビットが1であればシフトしてできる左側には1を詰めます。
最上位ビットが0であればシフトしてできる左側には0を詰めます。

例. sar / shrの違い

mov $0x80, %sil
sar $7, %sil # %sil = 0xFF
mov $0x80, %sil
shr $7, %sil # %sil = 0x01

Multiplication and division operations

Instruction Effect Description
imulq S S × R[%rax]→R[%rdx]:R[%rax] Signed full multiply
mulq S S × R[%rax]→R[%rdx]:R[%rax] Unsigned full multiply
cltq SignExtend(R[%eax])→R[%rax] Convert %eax to quad word
cqto SignExtend(R[%rax])→R[%rdx]:R[%rax] Convert to oct word
idivq S R[%rdx]:R[%rax] mod S→R[%rdx]
R[%rdx]:R[%rax]÷S→R[%rax]
Signed divide
divq S R[%rdx]:R[%rax] mod S→R[%rdx]
R[%rdx]:R[%rax]÷S→R[%rax]
Unsigned divide
  • sample
test.s
 .global main
format:
 .asciz "%016lx %016lx\n"
main:
 movabs $0xFFFFFFFFFFFFFFFF, %rax
 movabs $0xFFFFFFFFFFFFFFFF, %r10
 mul %r10 # %rdx:%rax
 mov $format, %rdi
 mov %rdx, %rsi
 mov %rax, %rdx
 xor %rax, %rax
 call printf
 ret

FLAGS register

CPUは直前に実行したinstructionに応じて状態をFLAGS registerに保持します。
FLAGS registerを使って処理を分岐させます。

pushf / popf instructionでFLAGS registerをstackにpush / popできます。
pushした値をmovでreadすることで FLAGS registerを読み込めます。

FLAGS registerのbit assignは次の通りです。

 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 
 -------------------------------------------------
 | | | | |OF|DF|IF|TF|SF|ZF| 0|AF| 0|PF| 1|CF|
 -------------------------------------------------

CF:Carry Flag
PF:Parity Flag
AF:Adjust Flag
ZF:Zero Flag
SF:Sign Flag
TF:Trap Flag
IF:Interrupt Enable Flag
DF:Direction Flag
OF:Overflow Flag

read方法は次の通りです。

 .global main
format:
 .asciz "0x%04x\n"
main:
 xor %rax, %rax
 pushf
 mov (%rsp), %ax
 popf
 mov %eax, %esi
 mov $format, %rdi
 xor %rax, %rax
 call printf
 ret

FLAGS registerをprintします。

test.s
 .global main
format:
 .asciz "ZF=%01x SF=%01x CF=%01x OF=%01x\n"
print_flags:
 xor %r13, %r13
 mov %di, %r13w
 mov $format, %rdi # 1st
 xor %rsi, %rsi # 2nd ZF
 bt $6, %r13w
 lahf
 mov %ah, %al
 mov %al, %sil
 and $0x01, %sil
 xor %rdx, %rdx # 3rd SF
 bt $7, %r13w
 lahf
 mov %ah, %al
 mov %al, %dl
 and $0x01, %dl
 xor %rcx, %rcx # 4th CF
 mov %r13b, %cxl
 and $0x01, %cxl
 xor %r8, %r8 # 5th OF
 bt $11, %r13w
 lahf
 mov %ah, %al
 mov %al, %r8b
 and $0x01, %r8b
 xor %rax, %rax
 call printf
 ret
main:
 xor %rdi, %rdi # %rdi=0
 mov $0x7f, %bl
 add $0x00, %bl
 pushf
 mov (%rsp), %di # %di=FLAGS register
 popf
 call print_flags
 ret

ZF:Zero Flag

0であれば1、そうでなければ0になります。

例.

mov $0x00 %bl
test %bl, %bl # ZF = 1
mov $0x01 %bl
test %bl, %bl # ZF = 0

SF:Sign Flag

負であれば1、そうでなければ0になります。
結果の最上位ビットを返します。負であれば最上位ビットは1です。

例.

mov $0x80 %bl
test %bl, %bl # SF = 1
mov $0x7f %bl
test %bl, %bl # SF = 0
mov $0x00 %bl
test %bl, %bl # SF = 0

CF:Carry Flag

符号なし演算としてcarry or a borrowが発生すれば1、そうでなければ0になります。

例. 符号なし 8bit range(0~255)

mov $0xff %bl
add $0x01 %bl # CF = 1, 255+1=256
mov $0xfe %bl
add $0x01 %bl # CF = 0, 254+1=255
mov $0x01 %bl
sub $0x01 %bl # CF = 0, 1-1=0
mov $0x01 %bl
sub $0x02 %bl # CF = 1, 1-2=-1

OF:Overflow Flag

符号付き演算としてcarry or a borrowが発生すれば1、そうでなければ0になります。

例. 符号付き 8bit range(-128~0~127), 0xff(-1)~0x80(-128)~0x7f(127)~0x00(0)

mov $0x7f %bl
add $0x01 %bl # OF = 1, 127+1=128
mov $0x7e %bl
add $0x01 %bl # OF = 0, 126+1=127
mov $0x80 %bl
sub $0x01 %bl # OF = 1, -128-1=-129
mov $0x81 %bl
sub $0x01 %bl # OF = 0, -127-1=-128

Condition Codes

FLAGS registerの組み合わせに応じてCondition Codes(cc)が決まります。
Condition Codesに対応したjump命令があります。

cmp / test命令を利用するとregisterを変更することなしにFLAGS registerを更新できます。

  • cmp s2,s1 : set flags based on s1 - s2
  • test s2,s1 : set flags based on s1&s2 (logical and)

cmp実行後のFLAGS registerとccの対応を次に示します。
ccに応じてjump命令が決まります。
フォーマットはjccとなります。例. cc=eであればjeとなります。

cc condition tested meaning after cmp
e ZF equal to zero
ne ˜ ZF not equal to zero
s SF negative
ns ˜ SF non-negative
g ˜ (SF xor OF) & ˜ ZF greater (< signed)
ge ˜ (SF xor OF) greater or equal (<= signed)
l SF xor OF less (signed <)
le (SF xor OF) | ZF less or equal (signed <=)
a ˜ CF & ˜ ZF above (< unsigned)
ae ˜ CF above or equal (<= unsigned)
b CF below (< unsigned)
be CF | ZF below or equal (<= unsigned)

sample code

printf

レジスタの値をprintfするだけのsimpleなAssembly Codeを示します。
esiをprintfします。

test.s
 .global main
main:
 mov $format, %rdi # 1st parameter
 mov $10, %rsi # 2nd parameter
 xor %rax, %rax #
 call printf # printf("%d\n", %rsi)
 ret
format:
 .asciz "%d\n"
gcc -O2 test.s -o test && ./test
10

rdtscp

rdtscp命令は次の2つの情報を読み出すことができます。

  • Time-Stamp Counter
  • Processor ID

命令実行後に次のレジスタが更新されます。

  • edx:eax : 64bit Time-Stamp Counter
  • ecx : Processor ID

Time-Stamp CounterはCPUのclockに合わせてインクリメントされるカウンタ値です。

sample codeを示します。rdtscpを連続して実行します。
1度目のrdtscp後にEAXを退避してすぐに2度目のrdtscpを実行します。
1度目と2度目の差分のカウンタ値をprintfします。

ばらつきがありますが最小で77 clockかかりました。

test.s
 .global main
main:
 movl $10, %r13d
loop:
 rdtscp
 mov %eax, %r8d
 rdtscp
 mov %eax, %r9d
 sub %r8d, %r9d
 mov $format, %rdi # 1st parameter
 mov %r9d, %esi # 2nd parameter
 mov %ecx, %edx # 3rd parameter
 xor %rax, %rax #-
 call printf # printf
 decl %r13d
 jne loop
 ret
format:
 .asciz "%03d %d\n"
console
$ ./test
088 1
077 1
088 1
099 1
088 1
...
44

Go to list of users who liked

34
0

Go to list of comments

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
44

Go to list of users who liked

34