Использование системных библиотек
Файл djb2.s необходимо слинковать компилятором C, а собранный в результате бинарный исполняемый файл должна обладать информацией о том, где располагаются динамические библиотеки. Этого можно достичь, задействовав переменную окружения QEMU_LD_PREFIX как показано ниже:
riscv64-linux-gnu-as djb2.s -o djb2.o riscv64-linux-gnu-gcc djb2.o -o djb2 export QEMU_LD_PREFIX=/usr/riscv64-linux-gnu/ qemu-riscv64-static djb2 Enter text: hallo Hash is 261095189
Можно просмотреть исполняемый файл с помощью утилиты objdump и увидеть, что исполняемый файл отличается от предыдущих примеров за счёт настройки среды окружения C.
Подсчёт символов
Следующая программа подсчитывает количество символов, слов и строк, задаваемых в стандартном вводе. В ней используется функция getchar библиотеки C, которая передаёт ASCII-коды вводимых символов. При подсчете слов предполагается, что слово представляет собой по крайней мере букву алфавита или десятичную цифру. Код перевода строки в Linux имеет ASCII-код 0xa, ASCII-коды десятичных цифр лежат в диапазоне от 0x30 до 0x39 для цифр от 0 до 9, буквы латинского алфавита в верхнем регистре имеют коды от 0x41 до 0x5a для букв от A до Z, и коды от 0x61 до 0x7a для букв нижнего регистра от a до z. Для детального анализа некоторых инструкций в приведённом исходном коде заданы номера строк.
00 # wordcount.s 01 .section .text 02 .globl main # run in C 'environment' 03 main: 04 addi sp, sp, -40 # store ra (return address) and saved regs on stack 05 sd ra, 0(sp) 06 sd s0, 8(sp) 07 sd s1, 16(sp) 08 sd s2, 24(sp) 09 sd s3, 32(sp) 10 11 li s0, 0 # counter chars 12 li s1, 0 # counter line feeds 13 li s2, 0 # counter words 14 li s3, 0 # indicator if current input is in word 15 16 loop: 17 call getchar # get input from stdin in a0 18 bltz a0, end # if end of file (eof is -1) goto end 19 20 addi s0, s0, 1 # count characters 21 22 li t0, 0xa # is linefeed (ascii 0xa)? 23 bne a0, t0, nolf # no -> continue 24 addi s1, s1, 1 # yes -> count 25 nolf: 26 27 # is this a word: char digit or alphabet? 28 addi t0, a0, -0x30 # digits go from 0x30 to 0x39 29 li t1, 0x9 # if (char-0x30) >= 0 and <= 0x9 then digit 30 bleu t0, t1, aldi # trick: treat negative value as unsigned 31 # value (or neg. as unsign.) > 0x9, continue 32 andi t0, a0, ~0x20 # 0x60 to range > 0x40, lower to upper cast 33 addi t0, t0, -0x41 # letter go from 0x41 to 0x5a 34 li t1, 0x19 # (char-0x41) >= 0 and <= 0x19, then alphabet 35 bleu t0, t1, aldi # trick again 36 # reached here, then not in word (anymore) 37 add s2, s2, s3 # count word, indicator is one if word else 0 38 li s3, 0 # clear indicator 39 j loop 40 41 aldi: 42 li s3, 1 # char is part of word, indicate for word counter 43 j loop 44 end: 45 la a0, result # print result 46 mv a1, s1 47 mv a2, s2 48 mv a3, s0 49 call printf 50 51 li a0, 0 52 53 ld s3, 32(sp) # restore saved regs. 54 ld s2, 24(sp) 55 ld s1, 16(sp) 56 ld s0, 8(sp) 57 ld ra, 0(sp) # restore ra 58 addi sp, sp,40 59 ret # return to caller 60 61 .section .rodata 62 result: 63 .asciz "Lines: %u Words: %u Chars: %u\n" # write out result
Инструкции на строках 04-09 и 53-58 сохраняют значения регистров ra и s0-s3 согласно ABI, регистры s0-s2 используются в роли счётчиков для подсчёта символов, слов и строк соответственно. Регистр s3 принимает значение 1, если входной символ представляет собой букву слова. Все эти регистры изначально инициализируются нулями в строках 11-14.
В цикле со строки 16 вызывается функция getchar. Если в результате вызова getchar было получено значение -1 (признак конца файла или конца водимых данных), происходит выход из цикла (строка 18). Если getchar вернула ASCII-код символа, увеличивается значение счётчика символов (строка 20). Если getchar вернула символ перевода строки, увеличивается счётчик строк (строки 22-24). Считанный символ проверяется на то, что он является числом (строки 27-30) или буквой (строки 32-36) и в случае, если он попадает в один из этих диапазонов, значение регистра s3 выставляется равным 1 (наличие 1 в регистре s3 говорит о том, что обнаружено слово) и осуществляется переход на новую итерацию цикла считывания (строки 42-43). Если же прочитанный символ не входит в требуемые диапазоны, значение регистра r3 добавляется к счётчику слов, и его значение становится равным 0.
Строки 28-30 и 32-35 определяют, находится ли символ в заданном диапазоне. Первая проверка - на попадание в диапазон от 0x30 до 0x39 (цифры), это достигается путём вычитания из кода символа значения 0x30 и проверки на то, что результат лежит в диапазоне от 0 до 9. При этом выполняется операция если-меньше-или-равно (bleu). Строки 33-35 ведут себя похожим образом для диапазона от 0x41 до 0x5a за исключением того, что в строке 32 очищается бит 5 рассматриваемого символа инструкцией andi с инверсией значения 0x20. Очищая этот бит, все значения, большие или равные 0x60, отображаются в диапазон от значения 0x40, а значения ниже 0x60 не затрагиваются. Это позволяет выполнить проверку в строках 33-35 также и для значений в диапазоне от 0x61 до 0x7a.
riscv64-linux-gnu-as -o wordcount.o wordcount.s riscv64-linux-gnu-gcc -o wordcount wordcount.o export QEMU_LD_PREFIX=/usr/riscv64-linux-gnu/ qemu-riscv64-static wordcount word Word .... hello next line should be two lines Lines: 2 Words: 9 Chars: 60
Проверка чисел на простоту
Следующий код запрашивает число и проверяет его на простоту, результат этой проверки выводится на экран. Программа реализует алгоритм "решето Эратосфена". C-функция scanf принимает аргумент - строку "%u", которая читает беззнаковое целое. Метки, начинающиеся с .L, являются локальными и не экспортируются в таблицу символов.
00 # prime.s 01 .equ maxnb, 0x100000 02 .section .text 03 .globl main # run in C 'environment' 04 main: 05 addi sp, sp, -8 # store ra (return address) on stack 06 sd ra, 0(sp) 07 08 la a0, prompt # printf the prompt string 09 call printf 10 11 la a0, scanfmt # scanf from stdin (console) 12 la a1, input # into buffer input 13 call scanf # with format scanfmt 14 15 blez a0, .Lerr # input error 16 17 la a1, input # check if input number n fits 18 lw a1, 0(a1) 19 li t0, maxnb 20 bge a1, t0, .Lerr 21 22 la a0, input # process input with sieve 23 call sieve 24 25 bnez a0, .Lp1 26 .Lp0: 27 la a0, outno 28 j .Lpp 29 .Lp1: 30 la a0, outyes # print result 31 j .Lpp 32 .Lerr: 33 la a0, error 34 .Lpp: 35 call printf 36 37 li a0, 0 38 39 ld ra, 0(sp) # restore ra 40 addi sp, sp,8 41 ret # return to caller 42 43 sieve: 44 # input: register a0 points to number n 45 # that is checked if it is a prime. 46 # output: if n is prime a0 is one else zero 47 # sieve of Erastosthenes 48 # init array with numbers 49 lw t1, 0(a0) # nb to check 50 li t2, 2 # counter start with 2 51 la t3, array # pointer to array 52 .Ls0: 53 sw t2, 8(t3) # set item to index, 8() is begin with index 2 54 addi t3, t3, 4 # increment by four for word size 55 addi t2, t2, 1 # counter 56 ble t2, t1, .Ls0 # until counter == nb to check 57 58 # array has now the values: 0 0 2 3 4 5 6 7 8 9 10... 59 60 # non-primes are cancelled out 61 # by setting their array items to zero 62 li t2, 2 # start with 2, t2 is index i 63 la t3, array # t3 is pointer to array 64 .Ls1: 65 lw t4, 8(t3) # t4 is current array item (offset by 2) 66 beqz t4, .Ls3 # no prime, continue at .Ls3 67 68 mul t4, t2, t2 # t4 = t2 * t2; t4 is index j 69 .Ls2: 70 slli t5, t4, 2 # t5 = t4 * 4 for offset (words) in array 71 add t5, t3, t5 # t5 = t3 + t5; t5 is address in array for j 72 sw x0, 0(t5) # set entry to 0, no prime nb, array[j] = 0 73 add t4, t4, t2 # t4 = t4 + t2; j += i 74 ble t4, t1, .Ls2 # cancel out all multiples of i for i < n 75 76 .Ls3: 77 addi t2, t2, 1 # continue with next number 78 mul t0, t2, t2 # as long as n*n > index 79 ble t0, t1, .Ls1 80 81 slli t0, t1, 2 # use n as index 82 add t0, t3, t0 # compute address in array 83 lw t0, 0(t0) # load its item 84 snez a0, t0 # set a0 to 1 if array[n] != 0 85 86 ret 87 88 89 .section .rodata 90 prompt: 91 .asciz "Enter number (<1048576): " 92 scanfmt: 93 .asciz "%u" # scanf an unsigned int number 94 outyes: 95 .asciz "is a prime number.\n" 96 outno: 97 .asciz "is not a prime number.\n" 98 error: 99 .asciz "wrong input.\n" 100 .section .bss 101 input: # storage for numbers 102 .word 0 103 array: 104 .zero 4*maxnb # max number storage
Строки 01-41 реализуют с вводом числа и выводом результата. Решето Эратосфена реализовано в строках 43-86. Строки 89-104 описывают данные, с которыми происходит работа.
Первая часть проверки чисел на простоту инициализирует массив (строки 49-56). Основная часть состоит в исключении всех кратных чисел, начиная с числа два (строки 62-79). Для этого обрабатывается каждый элемент массива и если он уже исключён (равен 0), берется следующий элемент (строки 64-66). В противном случае аннулируются все кратные числа, начиная с квадрата рассматриваемого числа (строки 68-74, внутренний цикл) и заканчивая самим введенным числом. Затем обрабатывается следующий элемент массива (строки 64-79, внешний цикл). Это продолжается до тех пор, пока рассматриваемое число меньше или равно введенному числу (строки 77-79). Элемент массива, соответствующий введённому числу, проверяется в конце на предмет того, равен он нулю (число не простое) или нет (число- простое) (строки 81-84).
riscv64-linux-gnu-as -o prime.o prime.s riscv64-linux-gnu-gcc -o prime prime.o export QEMU_LD_PREFIX=/usr/riscv64-linux-gnu/ qemu-riscv64-static prime Enter number (<1048576): 1034123 is a prime number.
Контрольные вопросы
- Где можно посмотреть, какие номера каким системным вызовам соответствуют?
- С помощью какой инструкции осуществляется системный вызов?
- Что регламентирует ABI, кроме соглашения о вызовах?
- Как должна быть организована программа на языке ассемблера, использующая glibc?
- Как передать Qemu информацию о расположении динамических библиотек?