Опубликован: 19.01.2025 | Доступ: свободный | Студентов: 1 / 0 | Длительность: 02:34:00
Лекция 6:

Использование системных библиотек

< Лекция 5 || Лекция 6: 12 || Лекция 7 >

Файл 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.

Контрольные вопросы

  1. Где можно посмотреть, какие номера каким системным вызовам соответствуют?
  2. С помощью какой инструкции осуществляется системный вызов?
  3. Что регламентирует ABI, кроме соглашения о вызовах?
  4. Как должна быть организована программа на языке ассемблера, использующая glibc?
  5. Как передать Qemu информацию о расположении динамических библиотек?
< Лекция 5 || Лекция 6: 12 || Лекция 7 >