2012-03-10 17 views
8

私はx86_64アセンブリを学習しようとしていますが、今日標準入出力を試していました。Learning assembly - echo program name(SYSCALL命令を使用して)STDINから入力を読み込むにはどうすればよいですか?特に、入力が常に整数であることを知っていて、それをレジスタに読み込みたいのですが?x86_64アセンブリのSTDINからの入力を読み取る方法は?

@Daniel Kozarの回答は、以下の通りです。LinuxでのSYSCALL命令のSTDINとSTDOUTの動作について理解できました。コンソール入力から数値を読み込み、その数値に対応するascii文字を出力する小さなプログラムを作成しようとしました。入力として65と入力すると、Aを出力として取得する必要があります。また、改行文字もあります。ここでは、標準入力から32ビットの符号なし10進整数を読み込むための私の試みで、計算のための整数として、それを格納し、書き込み:すべてであれば、他のいずれか:-)

section .text 
    global _start 

_start: 
    mov rdi, 0x0  ; file descriptor = stdin = 0 
    lea rsi, [rsp+8] ; buffer = address to store the bytes read 
    mov rdx, 0x2  ; number of bytes to read 
    mov rax, 0x0  ; SYSCALL number for reading from STDIN 
    syscall   ; make the syscall 

    xor rax, rax  ; clear off rax 
    mov rbx, [rsp+8] ; read the first byte read into rsp+8 by STDIN call to rbp 
    sub rbx, 0x30  ; Since this is read as a character, it is obtained as ASCII value, so subtract by 0x30 to get the number 
    and rbx, 0xff  ; This ensures that everything other than the last byte is set to 0 while the last byte is as is 
    mov rax, rbx  ; move this value to rax since we want to store the final result in rax 
    shl rbx, 0x1  ; We need to multiply this by 10 so that we can add up all the digits read so multiplying the number by 2 and then by 8 and adding them up, so multiply by 2 here 
    shl rax, 0x3  ; multiply by 8 here 
    add rax, rbx  ; add 8 times multiplied value with 2 times multiplied value to get 10 times multiplied value 
    mov rbx, [rsp+9] ; now read the next byte (or digit) 
    sub rbx, 0x30  ; Again get the digit value from ASCII value of that digit's character 
    and rbx, 0xff  ; clear higher bytes 
    add rax, rbx  ; Add this to rax as unit's place value 
    mov [rsp+8], rax ; Move the entire byte to rax 
    mov rdi, 0x1  ; file descriptor = stdout 
    lea rsi, [rsp+8] ; buffer = address to write to console 
    mov rdx, 0x1  ; number of bytes to write 
    mov rax, 0x1  ; SYSCALL number for writing to STDOUT 
    syscall   ; make the syscall 

    xor rax, rax  ; clear off rax 
    mov rax, 0xa  ; move the new line character to rax 
    mov [rsp+8], rax ; put this on the stack 
    mov rdi, 0x1  ; file descriptor = stdout 
    lea rsi, [rsp+8] ; buffer = address to write to console 
    mov rdx, 0x1  ; number of bytes to write 
    mov rax, 0x1  ; SYSCALL number for writing to STDOUT 
    syscall   ; make the syscall 

    mov rdi, 0  ; set exit status = 0 
    mov rax, 60  ; SYSCALL number for EXIT 
    syscall   ; make the syscall 

EDIT 2に役立ちますそれは元気に戻る。すべての

section .text 
     global _start 

_start: 
;Read from STDIN 
     mov rdi, 0x0  ; file descriptor = stdin = 0 
     lea rsi, [rsp+8] ; buffer = address to store the bytes read 
     mov rdx, 0xa  ; number of bytes to read 
     mov rax, 0x0  ; SYSCALL number for reading from STDIN 
     syscall   ; make the syscall 


; Ascii to decimal conversion 
     xor rax, rax  ; clear off rax 
     mov rbx, 0x0  ; initialize the counter which stores the number of bytes in the string representation of the integer 
     lea rsi, [rsp+8] ; Get the address on the stack where the first ASCII byte of the integer is stored. 

rnext: 
     mov rcx, [rsi] ; Read the byte on the stack at the address represented by rsi 
     cmp rcx, 0xa  ; Check if it is a newline character 
     je return  ; If so we are done 
     cmp rbx, 0xa  ; OR check if we have read 10 bytes (the largest 32 bit number contains 10 digits, so we will have to process at most 10 bytes 
     jg return  ; If so we are done 
     sub rcx, 0x30  ; For the byte read, subtract by 0x30/48 to get the value from the ASCII code. 0 == 0x30 in ASCII, 1 == 0x31 in ASCII and so on. 
     and rcx, 0xff  ; Clear off the higher order bytes to ensure there is no interference 
     mov rdx, rax  ; We need to multiple this by 10 to get the next byte which goes to the unit's place and this byte becomes the ten's value. So make a copy 
     shl rax, 0x3  ; Multiply the original by 8 (Shift left by 3 is multiply by 8) 
     shl rdx, 0x1  ; Multiply the copy by 2 (Shift left by 1 is multiply by 2) 
     add rax, rdx  ; Add these a * 8 + a * 2 to get a * 10. 
     add rax, rcx  ; Add the digit to be at the units place to the original number 
     add rsi, 1  ; Advance the memory address by 1 to read the next byte 
     inc rbx   ; Increment the digit counter 
     jmp rnext   ; Loop until we have read all the digits or max is reached. 

return: 
     push rax   ; Push the read number on to the stack 

; write New Line 
     mov rax, 0xa  ; move the new line character to rax 
     mov [rsp+8], rax ; put this on the stack 
     mov rdi, 0x1  ; file descriptor = stdout 
     lea rsi, [rsp+8] ; buffer = address to write to console 
     mov rdx, 0x1  ; number of bytes to write 
     mov rax, 0x1  ; SYSCALL number for writing to STDOUT 
     syscall   ; make the syscall 


; Convert from Decimal to bytes 
     xor rdx, rdx  ; Clear rdx which stores obtains a single digit of the number to convert to ASCII bytes 
     mov r8, 0x0  ; Initialize the counter containing the number of digits 

     pop rax   ; Pop the read number from the stack 
     mov rbx, 0xa  ; We store the divisor which is 10 for decimals (base-10) in rbx. rbx will be the divisor. 

wnext: 
     div rbx   ; Divide the number in rdx:rax by rbx to get the remainder in rdx 
     add rdx, 0x30 ; Add 0x30 to get the ASCII byte equivalent of the remainder which is the digit in the number to be written to display. 
     push rdx   ; Push this byte to the stack. We do this because, we get the individial digit bytes in reverse order. So to reverse the order we use the stack 
     xor rdx, rdx  ; Clear rdx preparing it for next division 
     inc r8   ; Increment the digits counter 
     cmp rax, 0x0  ; Continue until the number becomes 0 when there are no more digits to write to the console. 
     jne wnext  ; Loop until there aren't any more digits. 

popnext: 
     cmp r8, 0x0  ; Check if the counter which contains the number of digits to write is 0 
     jle endw   ; If so there are no more digits to write 
     mov rdx, 0x1  ; number of bytes to write 
     mov rsi, rsp  ; buffer = address to write to console 
     mov rdi, 0x1  ; file descriptor = stdout 
     mov rax, 0x1  ; SYSCALL number for writing to STDOUT 
     syscall   ; make the syscall 
     dec r8   ; Decrement the counter 
     pop rbx   ; Pop the current digit that was already written to the display preparing the stack pointer for next digit. 
     jmp popnext  ; Loop until the counter which contains the number of digits goes down to 0. 

endw: 
; write New Line 
     xor rax, rax  ; clear off rax 
     mov rax, 0xa  ; move the new line character to rax 
     mov [rsp+9], rax ; put this on the stack 
     mov rdi, 0x1  ; file descriptor = stdout 
     lea rsi, [rsp+9] ; buffer = address to write to console 
     mov rdx, 0x1  ; number of bytes to write 
     mov rax, 0x1  ; SYSCALL number for writing to STDOUT 
     syscall   ; make the syscall 

; Exit 
     mov rdi, 0  ; set exit status = 0 
     mov rax, 60  ; SYSCALL number for EXIT 
     syscall   ; make the syscall 
+1

使用しているOSは何ですか? Windows? DOS? Linux? – Gabe

+0

'syscall 'の使用はオペレーティングシステムに依存します。 – hirschhornsalz

+0

私はLinuxを使用しています。その正確なコードは私のために働く。 –

答えて

5

まず:アセンブリには変数はありません。何らかの種類のデータのためのラベルがあります。データは、設計上、型指定されていない - 少なくとも実際のアセンブラではなく、HLA(例えば、MASM)である。

標準入力からの読み込みは、システムコールreadを使用して行います。あなたがすでに述べた記事を読んでいると仮定し、x64 Linuxでシステムコールを呼び出す方法を知っています。あなたがNASM(またはその構文に似ているもの)を使用していて、のメモリを予約したアドレスbufferstdinの入力を保存すると、システムコールの実行は次のようになります。

xor eax, eax ; rax <- 0 (write syscall number) 
xor edi, edi ; rdi <- 0 (stdin file descriptor) 
mov rsi, buffer ; rsi <- address of the buffer 
mov edx, BUFSIZE ; rdx <- size of the buffer 
syscall ; execute 

戻ったら、raxにはsyscallの結果が含まれます。どのように動作するかについて詳しく知りたい場合はman 2 readに問い合わせてください。

整数をアセンブリ言語で解析するのは簡単ではありませんが、 readは、標準入力に表示されるプレーンバイナリデータのみを提供するので、整数値を自分で変換する必要があります。キーボードで入力したものは、ASCIIコード(または使用している他のエンコーディング - ここではASCIIと仮定しています)としてアプリケーションに送られます。したがって、データをASCIIエンコードされた10進数から2進数に変換する必要があります。

このようなものになり、通常のunsigned int型にこのような構造を変換するためのC関数:

unsigned int parse_ascii_decimal(char *str,unsigned int strlen) 
{ 
    unsigned int ret = 0, mul = 1; 
    int i = strlen-1; 
    while(i >= 0) 
    { 
     ret += (str[i] & 0xf) * mul; 
     mul *= 10; 
     --i; 
    } 
    return ret; 
} 

アセンブリにこれを変換(および署名された数値をサポートするように延びる)のための課題として残されていますリーダー。 :)

最後に、write syscallは、指定されたファイルディスクリプタに書き込まれるはずのデータを持つバッファへのポインタを常に渡す必要があります。したがって、改行を出力したい場合は、改行を含むバッファを作成する以外に方法はありません。

+0

あなたは素晴らしいです!ありがとう!そして、はい、私はNASMを使用しています。バッファを割り当てる代わりに、スタックに直接読み込むことができますか? mov rsiと同様、[rsp + 8]? 私は演習btw ;-)で作業します –

+0

'mov rsi、[rsp + 8]'は実際の内容をスタックからレジスタに移動します。あなたが望むのはアドレスです。この場合、 'lea rsi、[rsp + 8]'はうまく動作します。もちろん、読んだり書いたりするのにもスタックを使うことができます。 –

関連する問題