Post

Exercício I/O

Um bom começo para entrar no ramo de engenharia reversa, é entender como um computador funciona, hoje usamos majoritariamente arquitetura x86 e ARM. E uma forma que encontrei de estudar mais sobre “baixo nível” foi ler o livro “Programação em Baixo Nível” de Igor Zhirkov, onde ele aborda desde o começo da computação. Nos primeiros capítulos, após explicar o básico de Assembly, ele introduz um exercício onde o leitor precisa implementar uma simples biblioteca de Entrada e Saída de dados (a tabela das implementações está abaixo).

Cada função a ser implementada possui suas próprias características, e é necessário compreender bem sobre como os registradores funcionam e quais são as convenções para que o código seja mais funcional, estou longe de ser um profissional em Assembly, mas fui capaz de concluir o exercício e passar no teste (em python) que o autor disponibiliza para os leitores. Espero que goste da leitura.

Ambiente

Linux 5.10.16.3-microsoft-standard-WSL2 #1 SMP x86_64 x86_64 x86_64 GNU/Linux

NASM version 2.15.05

gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0

gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1

Biblioteca de entrada/saída

FunctionDefinition
exitAccepts an exit code and terminates current process.
string_lengthAccepts a pointer to a string and returns its length.
print_stringAccepts a pointer to a null-terminated string and prints it to stdout.
print_charAccepts a character code directly as its first argument and prints it to stdout.
print_newlinePrints a character with code 0xA.
print_uintOutputs an unsigned 8-byte integer in decimal format. We suggest you create a buffer on the stack and store the division results there. Each time you divide the last value by 10 and store the corresponding digit inside the buffer. Do not forget, that you should transform each digit into its ASCII code (e.g., 0x04 becomes 0x34).
print_intOutput a signed 8-byte integer in decimal format.
read_charRead one character from stdin and return it. If the end of input stream occurs, return 0.
read_wordAccepts a buffer address and size as arguments. Reads next word from stdin (skipping whitespaces into buffer). Stops and returns 0 if word is too big for the buffer specified; otherwise returns a buffer address. This function should null-terminate the accepted string.
parse_uintAccepts a null-terminated string and tries to parse an unsigned number from its start. Returns the number parsed in rax, its characters count in rdx.
parse_intAccepts a null-terminated string and tries to parse a signed number from its start. Returns the number parsed in rax; its characters count in rdx (including sign if any). No spaces between sign and digits are allowed.
string_equalsAccepts two pointers to strings and compares them. Returns 1 if they are equal, otherwise 0.
string_copyAccepts a pointer to a string, a pointer to a buffer, and buffer’s length. Copies string to the destination. The destination address is returned if the string fits the buffer; otherwise zero s returned.

Minha implementação

Por ser iniciante, algumas implementações podem estar ineficientes e/ou mal otimizadas.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
section .text

exit:
    mov rax, 60
    xor rdi, rdi
    syscall

string_length:
    xor rax, rax
    .loop:
    cmp byte [rdi+rax], 0
    je .end
    inc rax
    jmp .loop
    .end:
    ret

print_string:
    push rdi                ; inserir ponteiro/string na pilha

    call string_length
    pop rsi                 ; resgatar em outro registrador

    mov rdx, rax            ; comprimento
    mov rax, 1              ; write
    mov rdi, 1              ; stdout
    syscall
    ret

print_char:
    push rdi
    mov rdi, rsp

    call print_string
    pop rdi
    ret

print_newline:
    mov rdi, 10
    jmp print_char

print_uint:
    mov rax, rdi            ; rdi contém a string, mova para rax
    mov rdi, rsp            ; rsp contém o endereço do topo da pilha, mova para rdi
    push 0                  ; terminador nulo (8 bytes)
    sub rsp, 16             ; alocar espaço na pilha (buffer)

    dec rdi                 ; decrementar rdi para apontar para o final do buffer 
                            ; (próxima posição livre)
    mov r8, 10              ; valor a ser usado nas divisões
    .div_loop:
    xor rdx, rdx            ; zerar rdx para armazenar os restos das divisões
    div r8                  ; divisão: rax fica com o quociente, resto em rdx
    add dl, 0x30            ; adicionar 0x30 na menor parte de rdx (dl) 
    dec rdi                 ; ir para o próximo espaço livre do buffer
    mov [rdi], dl           ; mover o caractere ascii no local apontado

    test rax, rax
    jnz .div_loop

    call print_string

    add rsp, 24             ; restaurar a pilha
    ret

print_int:
    test rdi, rdi           ; verifica se rdi é igual a 0 para saber se é positivo
                            ; ou negativo
    jns print_uint          ; se for positivo, vá para print_uint
    push rdi                ; guarde rdi na pilha
    mov rdi, '-'            ; mover o sinal de negativo para rdi
    call print_char         ; chamar print_char para imprimir '-'
    pop rdi                 ; recuperar rdi da pilha
    neg rdi                 ; negativar rdi (torná-lo positivo nesse caso)
    call print_uint         ; chamar print_uint

read_char:
    push 0                  ; empilhar um valor inicial 
    mov rsi, rsp            ; apontar o buffer para o rsi
    mov rdx, 1              ; quantidade de bytes a serem lidos
    mov rdi, 0              ; stdin  
    mov rax, 0              ; read()
    syscall
    pop rax                 ; guardar o retorno em rax
    ret 

read_word:
    push r14                ; salvar o estado dos regs r14 e r15
    push r15                ; r14 será o contador de caracteres e r15 armazenará
                            ; o endereço do buffer

    xor r14, r14
    mov r15, rsi 
    dec r15                 ; técnica para comparar durante a leitura dos caracteres

    .check_first_chars:
    push rdi
    call read_char
    pop rdi

    cmp al, ' '             ; espaço em branco
    je .check_first_chars

    cmp al, 10              ; newline \n
    je .check_first_chars

    cmp al, 13              ; carriage return \r
    je .check_first_chars

    cmp al, 9               ; tab \t
    je .check_first_chars

    test al, al
    jz .end

    .readloop:
    mov byte [rdi + r14], al ; guardar o caractere
    inc r14 
    
    push rdi
    call read_char
    pop rdi   

    cmp al, ' '             ; espaço em branco
    je .end

    cmp al, 10              ; newline \n
    je .end

    cmp al, 13              ; carriage return \r
    je .end

    cmp al, 9               ; tab \t
    je .end

    test al, al 
    jz .end

    cmp r14, r15
    je .max 

    jmp .readloop

    .end:
    mov byte [rdi + r14], 0 ; caractere nulo 
    mov rax, rdi            ; preparar o 'ret' 
    mov rdx, r14            ; quantidade lida

    pop r14
    pop r15
    ret

    .max:
    xor rax, rax            ; devolver '0' se o máximo do buffer for atingido
    pop r14
    pop r15
    ret

; rdi points to a string
; returns rax: number, rdx : length
parse_uint:
    mov r8, 10              ; r8 será usado para multiplicações
    xor rax, rax
    xor rdx, rdx
    .parseloop:
    cmp byte [rdi], 0       ; ver se a string está no final
    jz .end

    cmp byte [rdi], '0'     ; ver se o caractere está abaixo de 0x30 ASCII
    jl .end
    cmp byte [rdi], '9'     ; ver se o caractere está acima de 0x39 ASCII
    ja .end

    movzx rcx, byte [rdi]   ; se chegou aqui, é um dígito, copie para rcx no 
                            ; tamanho correto (zero-extended)

    sub rcx, '0'            ; subtraia 0x30 para "converter" o ASCII
    push rdx
    mul r8                  ; multiplicar o valor de rax por 10
                            ; (e liberar um dígito à direita)
    pop rdx

    add rax, rcx            ; "cole" o valor convertido no espaço liberado

    inc rdx
    inc rdi

    jmp .parseloop

    .end:
    ret

; rdi points to a string
; returns rax: number, rdx : length
parse_int:
    cmp byte [rdi], '-'     ; comparar se o caractere atual é um sinal de negativo
    je .signed
    jmp parse_uint

    .signed:
    inc rdi                 ; ir para o próximo caractere
    call parse_uint
    neg rax                 ; inverta o sinal do número retornado pelo parse_uint

    test rdx, rdx           ; verificar se o comprimento do número é 0
    jz .error

    inc rdx                 ; se não for 0, adicione 1
    ret

    .error:
    xor rax, rax
    ret

string_equals:
    xor rax, rax
    mov r8b, byte [rdi]     ; mover o primeiro byte da string de origem para r8
    mov r9b, byte [rsi]     ; mover o primeiro byte da string de destino para r9
    cmp r8b,  r9b           ; comparar se são iguais
    jne .not_equal          ; se não são iguais, retorna 0
    cmp r8b, 0              ; comparar se a string de origem está no fim
    je .equal               ; se são iguais, retorna 1
    inc rdi                 ; ir para o próximo byte 
    inc rsi                 ; ir para o próximo byte 
    jmp string_equals

    .equal:
    mov rax, 1
    ret

    .not_equal:
    xor rax, rax
    ret

string_copy:
    xor rcx, rcx            ; zerar o comparador de comprimento
    cmp rdx, 0              ; ver se o comprimento é 0 ou negativo
    jle .not_good

    .copy_loop:
    cmp byte [rdi], 0       ; ver se a string de origem está no fim
    jz .end 
    mov al, byte [rdi]      ; copiar byte (no endereço) da string de origem para al
    mov byte [rsi], al      ; copiar byte de al para string de destino (no endereço)
    inc rdi                 ; ir para o próximo endereço
    inc rsi                 ; ir para o próximo endereço
    inc rcx                 ; aumentar comparador de comprimento

    cmp rcx, rdx            ; comparar os comprimentos
    jl .copy_loop           ; se não excedeu, continua o loop

    jmp .not_good           ; se excedeu, retorna 0

    .end:
    mov byte [rsi], 0       ; terminador nulo na string de destino
    mov rax, rsi            ; mover endereço final da string de destino para rax
    sub rax, rcx            ; subtrair a quantidade de bytes copiados para 
                            ; retornar o endereço inicial da string de destino
    ret

    .not_good:
    xor rax, rax
    ret

Solução do autor no Github

Arquivo test.py

Original do Github

Código modificado para otimizações de versão do python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
#!/usr/bin/python

from __future__ import print_function, division
try:
    bytes('foo', 'utf-8')
except:
    u8_bytes = lambda a: a
    u8_str = lambda a: a
else:
    u8_bytes = lambda a: bytes(a, 'utf-8')
    u8_str = lambda a: str(a, 'utf-8')

import os
import subprocess
import sys
import re
import sys
from subprocess import CalledProcessError, Popen, PIPE

try:
    from termcolor import colored
except ImportError:
    from termcolor_py2 import colored

#-------helpers---------------

def starts_uint( s ):
    matches = re.findall('^\d+', s)
    if matches:
        return (int(matches[0]), len(matches[0]))
    else:
        return (0, 0)

def starts_int( s ):
    matches = re.findall('^-?\d+', s)
    if matches:
        return (int(matches[0]), len(matches[0]))
    else:
        return (0, 0)

def unsigned_reinterpret(x):
    if x < 0:
        return x + 2**64
    else:
        return x

def first_or_empty( s ):
    sp = s.split()
    if sp == [] : 
        return ''
    else:
        return sp[0]

#-----------------------------

def compile( fname, text ):
    f = open( fname + '.asm', 'w')
    f.write( text )
    f.close()

    if subprocess.call( ['nasm', '-f', 'elf64', fname + '.asm', '-o', fname+'.o'] ) == 0 and subprocess.call( ['ld', '-o' , fname, fname+'.o'] ) == 0:
             print(' ', fname, ': compiled')
             return True
    else: 
        print(' ', fname, ': failed to compile')
        return False


def launch( fname, seed = '' ):
    output = ''
    try:
        p = Popen(['./'+fname], shell=None, stdin=PIPE, stdout=PIPE)
        (output, err) = p.communicate(input=u8_bytes(seed))
        return (u8_str(output), p.returncode)
    except CalledProcessError as exc:
        return (exc.output, exc.returncode)
    else:
        return (u8_str(output), 0)



def test_asm( text, name = 'dummy',  seed = '' ):
    if compile( name, text ):
        r = launch( name, seed )
        #os.remove( name )
        #os.remove( name + '.o' )
        #os.remove( name + '.asm' )
        return r 
    return None 

class Test:
    name = ''
    string = lambda x : x
    checker = lambda input, output, code : False

    def __init__(self, name, stringctor, checker):
        self.checker = checker
        self.string = stringctor
        self.name = name
    def perform(self, arg):
        res = test_asm( self.string(arg), self.name, arg)
        if res is None:
            return False
        (output, code) = res
        print('"', arg,'" ->',  res)
        return self.checker( arg, output, code )

before_call="""
mov rdi, -1
mov rsi, -1
mov rax, -1
mov rcx, -1
mov rdx, -1
mov r8, -1
mov r9, -1
mov r10, -1
mov r11, -1
push rbx
push rbp
push r12 
push r13 
push r14 
push r15 
"""
after_call="""
cmp r15, [rsp] 
jne .convention_error
pop r15
cmp r14, [rsp] 
jne .convention_error
pop r14
cmp r13, [rsp] 
jne .convention_error
pop r13
cmp r12, [rsp] 
jne .convention_error
pop r12
cmp rbp, [rsp] 
jne .convention_error
pop rbp
cmp rbx, [rsp] 
jne .convention_error
pop rbx

jmp continue

.convention_error:
    mov rax, 1
    mov rdi, 2
    mov rsi, err_calling_convention
    mov rdx,  err_calling_convention.end - err_calling_convention
    syscall
    mov rax, 60
    mov rdi, -41
    syscall
section .data
err_calling_convention: db "You did not respect the calling convention! Check that you handled caller-saved and callee-saved registers correctly", 10
.end:
section .text
continue:
"""
tests=[ Test('string_length',
             lambda v : """section .data
        str: db '""" + v + """', 0
        section .text
        %include "lib.inc"
        global _start
        _start:
        """ + before_call + """
        mov rdi, str
        call string_length
        """ + after_call + """
        mov rdi, rax
        mov rax, 60
        syscall""",
        lambda i, o, r: r == len(i)
         ),

        Test('print_string',
             lambda v : """section .data
        str: db '""" + v + """', 0
        section .text
        %include "lib.inc"
        global _start 
        _start:
        """ + before_call + """
        mov rdi, str
        call print_string
        """ + after_call + """

        mov rax, 60
        xor rdi, rdi
        syscall""", 
        lambda i,o,r: i == o),

        Test('print_char',
            lambda v:""" section .text
        %include "lib.inc"
        global _start 
        _start:
        """ + before_call + """
        mov rdi, '""" + v + """'
        call print_char
        """ + after_call + """
        mov rax, 60
        xor rdi, rdi
        syscall""", 
        lambda i,o,r: i == o),

        Test('print_uint',
            lambda v: """section .text
        %include "lib.inc"
        global _start 
        _start:
        """ + before_call + """
        mov rdi, """ + v + """
        call print_uint
        """ + after_call + """
        mov rax, 60
        xor rdi, rdi
        syscall""", 
        lambda i, o, r: o == str(unsigned_reinterpret(int(i)))),
        
        Test('print_int',
            lambda v: """section .text
        %include "lib.inc"
        global _start 
        _start:
        """ + before_call + """
        mov rdi, """ + v + """
        call print_int
        """ + after_call + """
        mov rax, 60
        xor rdi, rdi
        syscall""", 
        lambda i, o, r: o == i),

        Test('read_char',
             lambda v:"""section .text
        %include "lib.inc"
        global _start 
        _start:
        """ + before_call + """
        call read_char
        """ + after_call + """
        mov rdi, rax
        mov rax, 60
        syscall""", 
        lambda i, o, r: (i == "" and r == 0 ) or ord( i[0] ) == r ),

        Test('read_word',
             lambda v:"""
        section .data
        word_buf: times 20 db 0xca
        section .text
        %include "lib.inc"
        global _start 
        _start:
        """ + before_call + """
        mov rdi, word_buf
        mov rsi, 20 
        call read_word
        """ + after_call + """
        mov rdi, rax
        call print_string

        mov rax, 60
        xor rdi, rdi
        syscall""", 
        lambda i, o, r: i == o),

        Test('read_word_length',
             lambda v:"""
        section .data
        word_buf: times 20 db 0xca
        section .text
        %include "lib.inc"
        global _start 
        _start:
        """ + before_call + """
        mov rdi, word_buf
        mov rsi, 20 
        call read_word
        """ + after_call + """
        
        mov rax, 60
        mov rdi, rdx
        syscall""",
        lambda i, o, r: len(first_or_empty(i.strip())) == r or (len(first_or_empty(i)) == 0 and r == 0)),
        #lambda i, o, r: len(i) == r or len(i) > 19),

        Test('read_word_too_long',
             lambda v:"""
        section .data
        word_buf: times 20 db 0xca
        section .text
        %include "lib.inc"
        global _start 
        _start:
        """ + before_call + """
        mov rdi, word_buf
        mov rsi, 20 
        call read_word
        """ + after_call + """

        mov rdi, rax
        mov rax, 60
        syscall""", 
        lambda i, o, r: ( (not len(i) > 19) and r != 0 ) or  r == 0 ),

        Test('parse_uint',
             lambda v: """section .data
        input: db '""" + v  + """', 0
        section .text
        %include "lib.inc"
        global _start 
        _start:
        """ + before_call + """
        mov rdi, input
        call parse_uint
        """ + after_call + """
        push rdx
        mov rdi, rax
        call print_uint
        mov rax, 60
        pop rdi
        syscall""", 
        lambda i,o,r:  starts_uint(i)[0] == int(o) and r == starts_uint( i )[1]),
        
        Test('parse_int',
             lambda v: """section .data
        input: db '""" + v  + """', 0
        section .text
        %include "lib.inc"
        global _start 
        _start:
        """ + before_call + """
        mov rdi, input
        call parse_int
        """ + after_call + """
        push rdx
        mov rdi, rax
        call print_int
        pop rdi
        mov rax, 60
        syscall""", 
        lambda i,o,r: (starts_int( i )[1] == 0 and int(o) == 0) or (starts_int(i)[0] == int(o) and r == starts_int( i )[1] )),

        Test('string_equals',
             lambda v: """section .data
             str1: db '""" + v + """',0
             str2: db '""" + v + """',0
        section .text
        %include "lib.inc"
        global _start
        _start:
        """ + before_call + """
        mov rdi, str1
        mov rsi, str2
        call string_equals
        """ + after_call + """
        mov rdi, rax
        mov rax, 60
        syscall""",
        lambda i,o,r: r == 1),
 
        Test('string_equals not equals',
             lambda v: """section .data
             str1: db '""" + v + """',0
             str2: db '""" + v + """!!',0
        section .text
        %include "lib.inc"
        global _start
        _start:
        """ + before_call + """
        mov rdi, str1
        mov rsi, str2
        call string_equals
        """ + after_call + """
        mov rdi, rax
        mov rax, 60
        syscall""",
        lambda i,o,r: r == 0),

        Test('string_copy',
            lambda v: """
        section .data
        arg1: db '""" + v + """', 0
        arg2: times """ + str(len(v) + 1) +  """ db  66
        section .text
        %include "lib.inc"
        global _start 
        _start:
        """ + before_call + """
        mov rdi, arg1
        mov rsi, arg2
        mov rdx, """ + str(len(v) + 1) + """
        call string_copy
        """ + after_call + """
        mov rdi, arg2 
        call print_string
        mov rax, 60
        xor rdi, rdi
        syscall""", 
        lambda i,o,r: i == o),

        Test('string_copy_too_long',
            lambda v: """
            section .rodata
            err_too_long_msg: db "string is too long", 10, 0
            section .data
        arg1: db '""" + v + """', 0
        arg2: times """ + str(len(v)//2)  +  """ db  66
        section .text
        %include "lib.inc"
        global _start 
        _start:
        """ + before_call + """
        mov rdi, arg1
        mov rsi, arg2
        mov rdx, """ + str(len(v)//2) + """
        call string_copy
        test rax, rax
        jnz .good
        mov rdi, err_too_long_msg 
        call print_string
        jmp _exit
        .good:
        """ + after_call + """
        mov rdi, arg2 
        call print_string
        _exit:
        mov rax, 60
        xor rdi, rdi
        syscall""", 
        lambda i,o,r: o.find("too long") != -1 ) 
        ]


inputs= {'string_length' 
        : [ 'asdkbasdka', 'qwe qweqe qe', ''],
         'print_string'  
         : ['ashdb asdhabs dahb', ' ', ''],
         'print_char'    
         : "a c",
         'print_uint'    
         : ['-1', '12345234121', '0', '12312312', '123123'],
         'print_int'     
         : ['-1', '-12345234121', '0', '123412312', '123123'],
         'read_char'            
         : ['-1', '-1234asdasd5234121', '', '   ', '\t   ', 'hey ya ye ya', 'hello world' ],
         'read_word'            
         : ['-1'], # , '-1234asdasd5234121', '', '   ', '\t   ', 'hey ya ye ya', 'hello world' ],
         'read_word_length'     
         : ['-1', '-1234asdasd5234121', '', '   ', '\t   ', 'hey ya ye ya', 'hello world' ],
         'read_word_too_long'     
         : [ 'asdbaskdbaksvbaskvhbashvbasdasdads wewe', 'short' ],
         'parse_uint'           
         : ["0", "1234567890987654321hehehey", "1" ],
         'parse_int'                
         : ["0", "1234567890987654321hehehey", "-1dasda", "-eedea", "-123123123", "1" ],
         'string_equals'            
         : ['ashdb asdhabs dahb', ' ', '', "asd" ],
         'string_equals not equals' 
         : ['ashdb asdhabs dahb', ' ', '', "asd" ],
         'string_copy'   
         : ['ashdb asdhabs dahb', ' ', ''],
         'string_copy_too_long'   
         : ['ashdb asdhabs dahb', ' ', ''],
}
              
if __name__ == "__main__":
    found_error = False
    for t in tests:
        for arg in inputs[t.name]:
            if not found_error:
                try:
                    print('          testing', t.name,'on "'+ arg +'"')
                    res = t.perform(arg)
                    if res: 
                        print('  [', colored('  ok  ', 'green'), ']')
                    else:
                        print('* [ ', colored('fail', 'red'),  ']')
                        found_error = True
                except KeyError:
                    print('* [ ', colored('fail', 'red'),  '] with exception' , sys.exc_info()[0])
                    found_error = True
    if found_error:
        print('Not all tests have been passed')
    else:
        print(colored( "Good work, all tests are passed", 'green'))

Output gerado da minha implementação:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
          testing string_length on "asdkbasdka"
  string_length : compiled
" asdkbasdka " -> ('', 10)
  [   ok   ]
          testing string_length on "qwe qweqe qe"
  string_length : compiled
" qwe qweqe qe " -> ('', 12)
  [   ok   ]
          testing string_length on ""
  string_length : compiled
"  " -> ('', 0)
  [   ok   ]
          testing print_string on "ashdb asdhabs dahb"
  print_string : compiled
" ashdb asdhabs dahb " -> ('ashdb asdhabs dahb', 0)
  [   ok   ]
          testing print_string on " "
  print_string : compiled
"   " -> (' ', 0)
  [   ok   ]
          testing print_string on ""
  print_string : compiled
"  " -> ('', 0)
  [   ok   ]
          testing print_char on "a"
  print_char : compiled
" a " -> ('a', 0)
  [   ok   ]
          testing print_char on " "
  print_char : compiled
"   " -> (' ', 0)
  [   ok   ]
          testing print_char on "c"
  print_char : compiled
" c " -> ('c', 0)
  [   ok   ]
          testing print_uint on "-1"
  print_uint : compiled
" -1 " -> ('18446744073709551615', 0)
  [   ok   ]
          testing print_uint on "12345234121"
  print_uint : compiled
" 12345234121 " -> ('12345234121', 0)
  [   ok   ]
          testing print_uint on "0"
  print_uint : compiled
" 0 " -> ('0', 0)
  [   ok   ]
          testing print_uint on "12312312"
  print_uint : compiled
" 12312312 " -> ('12312312', 0)
  [   ok   ]
          testing print_uint on "123123"
  print_uint : compiled
" 123123 " -> ('123123', 0)
  [   ok   ]
          testing print_int on "-1"
  print_int : compiled
" -1 " -> ('-1', 0)
  [   ok   ]
          testing print_int on "-12345234121"
  print_int : compiled
" -12345234121 " -> ('-12345234121', 0)
  [   ok   ]
          testing print_int on "0"
  print_int : compiled
" 0 " -> ('0', 0)
  [   ok   ]
          testing print_int on "123412312"
  print_int : compiled
" 123412312 " -> ('123412312', 0)
  [   ok   ]
          testing print_int on "123123"
  print_int : compiled
" 123123 " -> ('123123', 0)
  [   ok   ]
          testing read_char on "-1"
  read_char : compiled
" -1 " -> ('', 45)
  [   ok   ]
          testing read_char on "-1234asdasd5234121"
  read_char : compiled
" -1234asdasd5234121 " -> ('', 45)
  [   ok   ]
          testing read_char on ""
  read_char : compiled
"  " -> ('', 0)
  [   ok   ]
          testing read_char on "   "
  read_char : compiled
"     " -> ('', 32)
  [   ok   ]
          testing read_char on "	   "
  read_char : compiled
" 	    " -> ('', 9)
  [   ok   ]
          testing read_char on "hey ya ye ya"
  read_char : compiled
" hey ya ye ya " -> ('', 104)
  [   ok   ]
          testing read_char on "hello world"
  read_char : compiled
" hello world " -> ('', 104)
  [   ok   ]
          testing read_word on "-1"
  read_word : compiled
" -1 " -> ('-1', 0)
  [   ok   ]
          testing read_word_length on "-1"
  read_word_length : compiled
" -1 " -> ('', 2)
  [   ok   ]
          testing read_word_length on "-1234asdasd5234121"
  read_word_length : compiled
" -1234asdasd5234121 " -> ('', 18)
  [   ok   ]
          testing read_word_length on ""
  read_word_length : compiled
"  " -> ('', 0)
  [   ok   ]
          testing read_word_length on "   "
  read_word_length : compiled
"     " -> ('', 0)
  [   ok   ]
          testing read_word_length on "	   "
  read_word_length : compiled
" 	    " -> ('', 0)
  [   ok   ]
          testing read_word_length on "hey ya ye ya"
  read_word_length : compiled
" hey ya ye ya " -> ('', 3)
  [   ok   ]
          testing read_word_length on "hello world"
  read_word_length : compiled
" hello world " -> ('', 5)
  [   ok   ]
          testing read_word_too_long on "asdbaskdbaksvbaskvhbashvbasdasdads wewe"
  read_word_too_long : compiled
" asdbaskdbaksvbaskvhbashvbasdasdads wewe " -> ('', 0)
  [   ok   ]
          testing read_word_too_long on "short"
  read_word_too_long : compiled
" short " -> ('', 0)
  [   ok   ]
          testing parse_uint on "0"
  parse_uint : compiled
" 0 " -> ('0', 1)
  [   ok   ]
          testing parse_uint on "1234567890987654321hehehey"
  parse_uint : compiled
" 1234567890987654321hehehey " -> ('1234567890987654321', 19)
  [   ok   ]
          testing parse_uint on "1"
  parse_uint : compiled
" 1 " -> ('1', 1)
  [   ok   ]
          testing parse_int on "0"
  parse_int : compiled
" 0 " -> ('0', 1)
  [   ok   ]
          testing parse_int on "1234567890987654321hehehey"
  parse_int : compiled
" 1234567890987654321hehehey " -> ('1234567890987654321', 19)
  [   ok   ]
          testing parse_int on "-1dasda"
  parse_int : compiled
" -1dasda " -> ('-1', 2)
  [   ok   ]
          testing parse_int on "-eedea"
  parse_int : compiled
" -eedea " -> ('0', 0)
  [   ok   ]
          testing parse_int on "-123123123"
  parse_int : compiled
" -123123123 " -> ('-123123123', 10)
  [   ok   ]
          testing parse_int on "1"
  parse_int : compiled
" 1 " -> ('1', 1)
  [   ok   ]
          testing string_equals on "ashdb asdhabs dahb"
  string_equals : compiled
" ashdb asdhabs dahb " -> ('', 1)
  [   ok   ]
          testing string_equals on " "
  string_equals : compiled
"   " -> ('', 1)
  [   ok   ]
          testing string_equals on ""
  string_equals : compiled
"  " -> ('', 1)
  [   ok   ]
          testing string_equals on "asd"
  string_equals : compiled
" asd " -> ('', 1)
  [   ok   ]
          testing string_equals not equals on "ashdb asdhabs dahb"
  string_equals not equals : compiled
" ashdb asdhabs dahb " -> ('', 0)
  [   ok   ]
          testing string_equals not equals on " "
  string_equals not equals : compiled
"   " -> ('', 0)
  [   ok   ]
          testing string_equals not equals on ""
  string_equals not equals : compiled
"  " -> ('', 0)
  [   ok   ]
          testing string_equals not equals on "asd"
  string_equals not equals : compiled
" asd " -> ('', 0)
  [   ok   ]
          testing string_copy on "ashdb asdhabs dahb"
  string_copy : compiled
" ashdb asdhabs dahb " -> ('ashdb asdhabs dahb', 0)
  [   ok   ]
          testing string_copy on " "
  string_copy : compiled
"   " -> (' ', 0)
  [   ok   ]
          testing string_copy on ""
  string_copy : compiled
"  " -> ('', 0)
  [   ok   ]
          testing string_copy_too_long on "ashdb asdhabs dahb"
  string_copy_too_long : compiled
" ashdb asdhabs dahb " -> ('string is too long\n', 0)
  [   ok   ]
          testing string_copy_too_long on " "
  string_copy_too_long : compiled
"   " -> ('string is too long\n', 0)
  [   ok   ]
          testing string_copy_too_long on ""
  string_copy_too_long : compiled
"  " -> ('string is too long\n', 0)
  [   ok   ]
Good work, all tests are passed

Lições aprendidas

Esse exercício deixou mais que claro pra mim como funcionam as syscalls, que são basicamente rotinas que o sistema operacional organiza para o usuário, pois não é ideal que um programa feito pelos usuários atinja alguma parte crítica do SO. Um aprendizado importante na minha visão nesse exercício foi que me fez criar uma analogia onde programação em Assembly é como uma “indústria” com várias engrenagens (os registradores), e cada engrenagem nessa indústria serve propósitos e usos próprios, e assim o programa segue uma “ordem” onde a entrada/saída de dados passa por essas engrenagens, numa linha de produção e sendo “operada” de acordo com o que o código pede.

Ainda há muito o que estudar, mas esse foi um passo importante quebrar qualquer medo ou pré-conceito da linguagem Assembly. Fique antenado nos próximos posts!

Obrigado por ler.

Esta postagem está licenciada sob CC BY 4.0 pelo autor.