Я экспериментирую с языком ассемблера RISC-V на эмуляторе (qemu64, Ubuntu для RISC-V).
Вот простая программа, ее функция — преобразовать строку instr в верхний регистр, outstr — результирующая строка.
.global _start
_start:
la x5, outstr
la x6, instr
loop:
lb x7, 0(x6)
addi x6, x6, 1
li x28, 'z'
bgt x7, x28, cont
li x28, 'a'
blt x7, x28, cont
addi x7, x7, ('A'-'a')
cont:
sb x7, 0(x5)
addi x5, x5, 1
li x28, 0
bne x7, x28, loop
li a0, 1
la a1, outstr
sub a2, x5, a1
li a7, 64
ecall
li a0, 0
li a7, 93
ecall
.data
instr: .asciz "String to conVErt xYz.\n"
outstr: .fill 255, 1, 0
Пока я рассматриваю самые две первые инструкции, где адрес outstr загружается в x5/t0, а адрес instr в x6/t1.
Дизассемблирование этих двух инструкций, предоставленное GDB, следующее:
0x00000000000100e8 <+0>: addi t0,gp,-2024
0x00000000000100ec <+4>: auipc t1,0x1
0x00000000000100f0 <+8>: addi t1,t1,84 # 0x11140
Итак, согласно первой инструкции, мы ожидаем t0 = (gp-2024)
Получим адрес переменной outstr:
(gdb) info variables
All defined variables:
Non-debugging symbols:
0x0000000000011140 __DATA_BEGIN__
0x0000000000011140 instr
0x0000000000011158 outstr
0x0000000000011257 __SDATA_BEGIN__
0x0000000000011257 __bss_start
0x0000000000011257 _edata
0x0000000000011258 __BSS_END__
0x0000000000011258 _end
outstr хранится по адресу 0x11158.
Давайте получим значение t0, которое должно быть адресом outstr:
(gdb) info registers x5
x5 0x55555567c3ac 93824993444780
Что-то не так, что случилось? Давайте получим значение gp:
(gdb) info register gp
gp 0x55555567cb94 0x55555567cb94
Это значение странное.
Как и ожидалось, имеем t0 = (gp-2024); 0x55555567cb94-2024 = 0x55555567c3ac; инструкция addi возвращает правильный результат.
Но t0 — это не адрес outstr! Это приводит при попытке доступа к outstr с использованием адреса, хранящегося в t0, к ошибке сегментации (что имеет смысл). Проблема возникает из-за того, что для регистра gp установлено неожиданное значение, но я не понимаю, почему. У кого-нибудь есть ключ ?
Спасибо.
Обновлено: добавление Makefile
OBJS = chapter5_ToUppercase.o
DEBUGFLAGS = -g
%.o : %.S
as $(DEBUGFLAGS) $< -o $@
chapter5_ToUppercase: $(OBJS)
ld -o chapter5_ToUppercase $(OBJS)
Попробуйте запустить обычную очень маленькую однострочную main
программу на языке C. Вы можете посмотреть, делает ли _start
что-нибудь относительно инициализации gp
, чтобы выяснить, кто что делает.
В спецификации abi, которую я нашел, говорится: «Предполагается, что код запуска программы загрузит значение символа __global_pointer$ в регистр gp»
@Jester Я проверю еще раз, но я почти уверен, что программа в это время работала, хотя я также был удивлен возвращенными адресами. Ошибка находится в метке cont: sb x5, 0(x5) Думаю, перепроверю. Но я думаю, что проблема связана с тем, что вы сказали про инициализацию или gp. Я думал, что это каким-то образом сделал компилятор или компоновщик, но никогда не проверял, гарантировано ли это.
@ErikEidt Я предполагал, что это каким-то образом было сделано автоматически, но я никогда не проверял, было ли это гарантировано. Я сделаю тест, который вы описали, спасибо
@Jester, что касается твоего последнего комментария, это определенно проблема, я никогда не инициализировал gp и не знал, что должен был это сделать. Мне следовало взглянуть на спецификацию Abi, спасибо за информацию. Я сделаю это и сообщу, решило ли это проблему или есть еще одна дополнительная проблема.
@Jester, ок, теперь я могу подтвердить, что проблема была связана с неинициализированным глобальным указателем, как вы сказали, я отредактирую свой вопрос и опубликую решение; еще раз спасибо
РЕШЕНИЕ: проблема возникла из-за того, что глобальный указатель gp не был инициализирован.
Чтобы решить эту проблему, мне пришлось сначала отредактировать сценарий компоновщика и определить значение инициализации регистра:
.data :
{
__DATA_BEGIN__ = .;
PROVIDE_HIDDEN (__my_gp = . + 0x800);
*(.data .data.* .gnu.linkonce.d.*)
SORT(CONSTRUCTORS)
}
Поскольку непосредственные значения RISCV представляют собой 12-битные значения со знаком (+/- 0x800), мы устанавливаем значение gp равным (.data + 0x800).
Собственно, на этом этапе мы определили, каким будет начальное значение gp, но не инициализировали gp. Для этого мы должны указать RISCV загрузить gp со значением, которое мы определили в скрипте компоновщика:
_start:
.option norelax
la gp, __my_gp
.option relax
la x5, outstr
la x6, instr
Обратите внимание, что перед записью в регистр gp необходимо отключить опцию norelax. Мне потребовался час, чтобы понять, почему инструкция la gp, __my_gp не работает...
Спасибо всем за вашу помощь.
Вы сбросили адрес
outstr
до инициализации процесса. Таким образом, вы смотрите на смещение файла, а не на виртуальный адрес. Какая инструкция неисправна и каково значение в соответствующем регистре в этот момент? Тем не менее, гарантированно лиgp
инициализируется ОС?