[/] Ein paar Fragen zu Assembler

Post Reply
Message
Author
User avatar
Lateralus
prolinux-forum-admin
Posts: 1238
Joined: 05. May 2004 7:35

[/] Ein paar Fragen zu Assembler

#1 Post by Lateralus »

Mahlzeit

Ich habe mich dann doch einmal auf Assembler eingelassen. Ich möchte dabei ein kleines Programm schreiben, was von stdin liest und nach stdout schreibt, was in C so aussehen könnte:

cat.c

Code: Select all

#include <stdio.h>

int main&#40;&#41;&#123;
        char c;
        while&#40; &#40;c=getc&#40;stdin&#41;&#41; != EOF&#41;
                putc&#40;c, stdout&#41;;

        return 0;
&#125;

Der erste Schritt war natürlich ein "hello world!" (nutze Intel-Syntax):

hello.asm

Code: Select all

; from&#58; Linux Assembly HOWTO

section .data                           ; section declaration

msg     db      "Hello, world!", 0xa    ; our dear string 0ax is '\n'
len     equ     $ - msg                 ; length of our dear string

section .text                           ; section declartion
                                        ; a program needs at least .text

                                        ; we must export the entry point
                                        ; to the ELF linker or loader. They
        global _start                   ; conventionally recognize start as
                                        ; their entry point. Use ld -e foo
                                        ; to override. the default

_start&#58;

; write our string to stdout

; write&#40;2&#41; &#91; von mir eingefügt&#93;
; #include <unistd.h>
;
;       ssize_t write&#40;int fd, const void *buf, size_t count&#41;;
;               eax       ebx             ecx         edx


        mov     edx, len        ; third argument&#58; message length
        mov     ecx, msg        ; second argument&#58; pointer to message to write
        mov     ebx, 1          ; first argument&#58; file handle &#40;stdout == 1&#41;
        mov     eax,4           ; system call number &#40;sys_write&#41; &#91; nachzulesen in /usr/include/asm/unistd.h&#93;
        int     0x80            ; call kernel

; and exit

        mov     ebx, 0          ; first syscall argument&#58; exit code
        mov     eax, 1          ; system call number &#40;sys_exit&#41;
        int     0x80            ; call kernel

Das ist soweit alles verständlich. Der nächste Schritt war für mich, anzuschauen, wie man Speicher reserviert. Dazu habe ich folgenden c-code kompiliert und disassemblet:

read.c

Code: Select all

void main&#40;&#41;&#123;
        char buf&#91;8&#93;;
        buf&#91;7&#93; = '\n';
&#125;
Ausgabe von gdb, ein bisschen kommentiert:

Code: Select all

&#40;gdb&#41; disassemble main
Dump of assembler code for function main&#58;
0x08048354 <main+0>&#58;    push   %ebp                     ; Inhalt von ebp auf Stack pushen
0x08048355 <main+1>&#58;    mov    %esp,%ebp                ; esp &#40;Stackpointer&#41; nach ebp schreiben
0x08048357 <main+3>&#58;    sub    $0x8,%esp                ;  0x8 von esp subtrahieren <- sind das unsere 8 Bytes?
0x0804835a <main+6>&#58;    and    $0xfffffff0,%esp         ; logisches UND auf esp und 0xfffffff0 ausfuehren
                                                        ; Das heißt&#58; die letzten zwei byte von esp auf Null setzen
                                                        ; Wohin wird das Ergebnis geschrieben? nach esp?
0x0804835d <main+9>&#58;    mov    $0x0,%eax                ; 0x0 nach eax schreiben
0x08048362 <main+14>&#58;   sub    %eax,%esp                ; esp von eax abziehen und in eax schreiben  
0x08048364 <main+16>&#58;   movb   $0xa,0xffffffff&#40;%ebp&#41;    ; 0xa an die letzte Stelle von ebp schreiben
                                                        ; &#40; buf&#91;7&#93; = '\n' &#41;
0x08048368 <main+20>&#58;   leave                           ; ???
0x08048369 <main+21>&#58;   ret                             ; return
0x0804836a <main+22>&#58;   nop                             ; keine Operation
0x0804836b <main+23>&#58;   nop
0x0804836c <main+24>&#58;   nop
0x0804836d <main+25>&#58;   nop
0x0804836e <main+26>&#58;   nop
0x0804836f <main+27>&#58;   nop
End of assembler dump.
Also den nop-Teil verstehe ich. ;-)

So wie ich das sehe, beinhaltet esp die Addresse buf[7], zeigt also auf das Ende des Array. Das geht so ziemlich aus der Zeile <main+16> hervor. Ist das korrekt? Dann müsste man von dieser Addresse nur 7 Byte abziehen und würde auf buf[0] == buf zeigen. Das wäre dann 0xffffffc7(ebp).

Allerdings blicke ich im Gesamten irgendwie nicht durch. Ich habe zwar schon den Assembler-Code mit einem

Code: Select all

gcc -S -masm=intel --static -o cat.S cat.c
erzeugt, aber da sehe ich noch weniger durch.


Die allgemeine Frage ist also: Wie realisiere ich, mir 1. Speicher zu verschaffen, 2. darauf per syscall zuzugreifen bzw. mir die richtige Addrese zu suchen und 3. das auch noch irgendwie mit einer Abfrage nach EOF in Verbindung zu bringen.


Desweiteren hätte ich die Frage, was ihr für eine Syntax bevorzugt - Intel oder AT&T - und natürlich aus welchen Gründen.


Gruß
Lateralus
Last edited by Lateralus on 28. Jun 2005 6:42, edited 1 time in total.

klopskuchen
prolinux-forum-admin
Posts: 1444
Joined: 26. Jun 2004 21:18
Contact:

#2 Post by klopskuchen »

> Dann müsste man von dieser Addresse nur 7 Byte abziehen und würde auf buf[0] == buf zeigen.
In diesem Fall (char) ja. Ansonsten
Datentyp belegt n byte
Nullbyte (bzw. Terminierung) - array[n] = pointer auf erstes arrayelement

> Allerdings blicke ich im Gesamten irgendwie nicht durch. Ich habe zwar schon den Assembler-Code mit einem ...
> erzeugt, aber da sehe ich noch weniger durch.
Vieleicht sind nasm -arg && ld -arg freundlicher gegenüber Lernwilligen?

> Die allgemeine Frage ist also: Wie realisiere ich, mir 1. Speicher zu verschaffen, 2. darauf per syscall zuzugreifen
> bzw. mir die richtige Addrese zu suchen und 3. das auch noch irgendwie mit einer Abfrage nach EOF in Verbindung zu bringen.
Eine konkrete Antwort kann ich leider nicht geben, da sich mein Eifer nach groben Überblicken eines in den gdb gestopften Programmes erschöpft. Ohne selbst gesucht zu haben, guck mal unter den Kernelaufrufen:
http://unusedino.de/linuxassembly/syscall.html

> Desweiteren hätte ich die Frage, was ihr für eine Syntax bevorzugt - Intel oder AT&T - und natürlich aus welchen Gründen.
Verstehen kann man beide. Ich persönlich find die Intel-Syn. einfacher zu handhaben (weniger Tipperei, imo übersichtlicher-> Lesbarkeit). Die AT&T-Syntax hab ich mir allerdings als erstes näher angesehen: gcc-inline.
Übrigens schwirrt irgendwo im Netz ein Konverter Intel-> gas herum. Url entfallen. :-/

Soweit der Senf eines (bisweilen faulen) asm-noobs ;)

HTH, Klopskuchen
When all else fails, read the instructions .

User avatar
Lateralus
prolinux-forum-admin
Posts: 1238
Joined: 05. May 2004 7:35

#3 Post by Lateralus »

@klopskuchen: Danke für die Hilfe, allerdings habe ich die -arg-Option weder bei ld noch nasm gefunden... Ab welcher Version gibt's das und was genau macht es?

Also, das cat.asm ist fertig. Der Vergleich, ob EOF erreicht ist, orientiert sich daran, wieviel Bytes read(2) als gelesen zurückgibt. Den benötigten Puffer habe ich in die .data Section gelegt und 64 Byte groß gemacht - sonst muss man für jedes gelesene Byte in der Kern springen, was eine unglaubliche Verschwendung an Rechenzeit ist. Man kann den Speicher wohl auch irgendwie im Stack besorgen, indem man "einfach" Stack- und Basepointer manipuliert, aber das ging mir dann zu weit, will sagen: das verstand ich noch nicht so recht.

So sieht's dann aus:

Code: Select all

section .data

; Unser Puffer
buf	times 64 db	'c'
len	equ		$ - buf

section .text

	global _start

_start&#58;

; Linux Systemaufrufe lesen ihre Argumente aus den Registern
; Die Nummer des Systemaufrufs wird in eax uebergeben und ist in /usr/include/asm/unistd.h nachzulesen
; Der Kernsprung erfolgt ueber den Software-Interrupt 0x80

.read&#58;
	; ssize_t read&#40;int fd, void *buf, size_t count&#41;
	mov	eax, 0x3	; #define __NR_read   3
	mov	ebx, 0x0	; stdin
	mov ecx, buf		; Addresse von buf
	mov	edx, len	; Anzahl der zu lesenden Byte == len
	int	0x80		; Kernel rufen
	
.check&#58;
	cmp	eax, 0x0	; 0 Byte gelesen - EOF
	je	.exit		; jump if equal
	jmp	.write

.write&#58;
	; ssize_t write&#40;int fd, const void *buf, size_t count&#41;;
	mov	edx, eax	; Anzahl der gelesenen Bytes aus eax in edx schreiben
	mov	eax, 0x4	; #define __NR_write   4
	mov	ebx, 0x1	; stdout
	; Addresse von buf liegt schon in ecx - nichts daran aendern
	int	0x80

	jmp	.read

.exit&#58;
	mov	eax, 0x1	; #define __NR_exit   1
	mov	ebx, 0x0	; exit&#40;0&#41;
	int	0x80

User avatar
hjb
Pro-Linux
Posts: 3264
Joined: 15. Aug 1999 16:59
Location: Bruchsal
Contact:

#4 Post by hjb »

Hi,

64-Byte-Puffer? Effizient wird es erst ab 4096 Bytes, eine größere Zweierpotenz ist zu empfehlen. Jedenfalls dann, wenn man direkt aus Dateien liest.

Speicherallokierung ist Sache der Anwendung. ob du eine malloc-Bibliothek verwendest oder alles selbst verwaltest, bleibt dir überlassen. Der einzige Syscall, der damit zu tun hat, ist brk, der die Obergrenze des virtuellen Speichers festlegt.

Gruß,
hjb
Pro-Linux - warum durch Fenster steigen, wenn es eine Tür gibt?

klopskuchen
prolinux-forum-admin
Posts: 1444
Joined: 26. Jun 2004 21:18
Contact:

#5 Post by klopskuchen »

> allerdings habe ich die -arg-Option weder bei ld noch nasm gefunden...
Missverständnis. Ich meinte die Verwendung von nasm und ld (mit argumenten) anstatt des gcc.

MfG, Klopskuchen
When all else fails, read the instructions .

Post Reply