Сборка RISC-V: глобальный указатель имеет странное значение

Я экспериментирую с языком ассемблера 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)

Вы сбросили адрес outstr до инициализации процесса. Таким образом, вы смотрите на смещение файла, а не на виртуальный адрес. Какая инструкция неисправна и каково значение в соответствующем регистре в этот момент? Тем не менее, гарантированно ли gp инициализируется ОС?

Jester 06.07.2024 00:07

Попробуйте запустить обычную очень маленькую однострочную main программу на языке C. Вы можете посмотреть, делает ли _start что-нибудь относительно инициализации gp, чтобы выяснить, кто что делает.

Erik Eidt 06.07.2024 00:44

В спецификации abi, которую я нашел, говорится: «Предполагается, что код запуска программы загрузит значение символа __global_pointer$ в регистр gp»

Jester 06.07.2024 00:49

@Jester Я проверю еще раз, но я почти уверен, что программа в это время работала, хотя я также был удивлен возвращенными адресами. Ошибка находится в метке cont: sb x5, 0(x5) Думаю, перепроверю. Но я думаю, что проблема связана с тем, что вы сказали про инициализацию или gp. Я думал, что это каким-то образом сделал компилятор или компоновщик, но никогда не проверял, гарантировано ли это.

Wheatley 06.07.2024 13:00

@ErikEidt Я предполагал, что это каким-то образом было сделано автоматически, но я никогда не проверял, было ли это гарантировано. Я сделаю тест, который вы описали, спасибо

Wheatley 06.07.2024 13:06

@Jester, что касается твоего последнего комментария, это определенно проблема, я никогда не инициализировал gp и не знал, что должен был это сделать. Мне следовало взглянуть на спецификацию Abi, спасибо за информацию. Я сделаю это и сообщу, решило ли это проблему или есть еще одна дополнительная проблема.

Wheatley 06.07.2024 13:07

@Jester, ок, теперь я могу подтвердить, что проблема была связана с неинициализированным глобальным указателем, как вы сказали, я отредактирую свой вопрос и опубликую решение; еще раз спасибо

Wheatley 06.07.2024 16:32
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
7
86
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

РЕШЕНИЕ: проблема возникла из-за того, что глобальный указатель 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 не работает...

Спасибо всем за вашу помощь.

Другие вопросы по теме