Обновить
16K+
16

Пользователь

48
Рейтинг
6
Подписчики
Отправить сообщение

Да, свалил в кучу. У меня они в списке стояли рядом, а это разные этажи: ядро и qemu-user читают e_entry запускаемой программы, а -bios и loader,cpu-num=0 задают счётчик команд машины при сбросе. Из одной строчки этого не видно.

Полез посмотреть, насколько разные, и залип. У PIE e_entry вообще не адрес. readelf показывает 0x1060 и не меняется никогда, а куда ядро реально уходит:

$ LD_SHOW_AUXV=1 ./a.out | grep AT_ENTRY
AT_ENTRY: 0x55580d478060
AT_ENTRY: 0x564f7a121060
AT_ENTRY: 0x556d3ed37060

Три запуска, три адреса. Все на 060, потому что база выровнена по странице, а 0x1060 к ней прибавляется. Смещение, а не адрес.

Под qemu-user база не прыгает, гость всегда 0x555555557060. Там, кстати, строк AT_ENTRY выходит две, и я сначала решил наугад, какая чья. Потом сообразил прогнать qemu-x86_64 --version: он печатает одну, и она каждый раз новая. Значит первая это сам qemu, вторая гость.

А у меня на стенде базы нет вообще. Без MMU что в файле, то и в памяти. Получается, случая три, а не два. Или я сам себя запутал, поправьте, если так.

Про gdb вы правы. Я это пробовал выяснить и бросил: в руководстве про счётчик команд ни слова или я не нашел, а до исходника не добрался, sourceware отдавал мне заглушку. Сейчас достал с зеркала на gitlab, gdb/symfile.c, generic_load:

CORE_ADDR entry = bfd_get_start_address (loadfile_bfd.get ());
...
regcache_write_pc (get_thread_regcache (inferior_thread ()), entry);

Ставит. Между этими строками там ещё шесть, и в них gdb печатает «Start address …», то есть он про точку входа даже вслух говорит.

Вторую форму loader я вообще не пробовал. Проверил на своей ловушке, она печатает X по 0x80000000, а _start у неё на 0x8000001c:

-device loader,file=decoy.elf                     X
  + -device loader,addr=0x8000001c,cpu-num=0      fib(1..10): 1 1 2 3 5 8 13 ...
  + -device loader,addr=0x80000000,cpu-num=0      X

Работает ровно как вы написали.

Про «-kernel и не должен» спорить не буду, тем более с вашим опытом и знаниями. Я описал следствие, вы назначение.

Раз уж полез в boot.c, мелочь: инструкций там 6, reset_vec[0…5], дальше данные, start_addr в [6] и dtb в [8]. Я это место когда-то дампил через монитор, сошлось.

За строчку про физику спасибо отдельно, платы у меня нет и вопрос так и висел в заметках.

Да, я это место не проверил. Сейчас выбрал время и разобрался.

Поставил qemu-user и прогнал все четыре сочетания:

точка входа цела,     +x    ok
точка входа сдвинута, +x    сигнал 11
точка входа цела,     644   отказ, код 1
точка входа сдвинута, 644   отказ, код 1

Без права на исполнение отказ одинаковый и от точки входа не зависит вообще. С правом всё решает точка входа. Так что вы правы, а моё предположение про то, что qemu-user бракует файл на разборе, было не верно, он его грузит и уходит по подменённому адресу.

А заодно нашлось, почему сообщение такое обманчивое. linux-user/linuxload.c: prepare_binprm за отсутствующий +x возвращает честный -EACCES, но вызывающий loader_exec делает так:

retval = prepare_binprm(bprm);
if (retval < 4) {
    return -ENOEXEC;
}

prepare_binprm при успехе возвращает число прочитанных байт, поэтому любой отрицательный код меньше четырёх и превращается в -ENOEXEC. Отсюда и Exec format error вместо Permission denied. У меня на 8.2.2 сообщения нет, просто код 1, так что тут ещё и от версии зависит.

Итого ваш опыт дал готовый список тех, кто точку входа читает: ядро Linux и qemu-user. С моей стороны к ним U-Boot и сам QEMU через -bios или loader с cpu-num=0. На стенде нет ни одного из них, поэтому там её и не читает никто.

Занятно, что расхождение вышло не в результатах, а в том, кто какой способ запуска держал в голове. Прогоны-то у всех сошлись, спор был про -kernel против -bios и loader, то есть про разные машины. Ну и тексты ошибок, как видно, ещё и от версии зависят.

Спасибо, что полезли проверять руками.

У вас всё получилось. Просто результат читается наоборот: вы показали, что в Linux точку входа как раз читают. Повторил ваш опыт на Ubuntu, тот же PIE, тот же сдвиг восьми байт по смещению 24:

оригинал, +x                     ok
точка входа сдвинута, +x         Segmentation fault
точка входа сдвинута, без +x     Permission denied

Permission denied тут не про заголовок. Файл с нетронутым заголовком и снятым правом на исполнение даёт ту же самую ошибку: хекс-редактор сохранил новый файл без +x. Сделайте chmod +x, и получите Segmentation fault, а это и есть доказательство, что заголовок прочитали и ушли по нему в пустоту.

Про Exec format error под qemu-x86_64 уверенности нет, поэтому предположением: у PIE сегменты LOAD кончаются в районе 0x4000, а 0x101060 лежит за пределами всего образа, и загрузчик qemu-user, видимо, бракует файл ещё на разборе. Сам проверить не смог, qemu-user под рукой не встал.

Где заголовок читают, там поле работает. У меня стенд стоит на -bios none, то есть без всякой прошивки и загрузчика, и читать его там просто некому. Как на плате, куда залили сырой образ. Но мне это еще стоит проверить на живой плате, но как писал ранее это не точно. Всё зависит от того что смогу ли я получить плату.

Проверил -bios, работает, спасибо.
Статья вот про что: машина стартует туда, куда зашито, и файл на это не влияет, ровно как на кристалле (по крайней мере я на это надеюсь). Ради этого весь стенд и стоит на -bios none. А -bios и loader с cpu-num=0 это возможности эмулятора, которых у железа нет: они дают файлу распорядиться тем, откуда машина стартует. В статье это надо было сказать прямо, а одна фраза там и вовсе говорит «QEMU не читает» вместо «-kernel не читает». Допишу дополнение в текст.

Проверил, вы правы: с cpu-num=0 точка входа читается. Одну фразу в статье надо было сказать точнее, там «QEMU не читает», а верно «-kernel не читает».

А пока проверял, нашлось кое-что поинтереснее. Вот слово по 0x1018, которое переходник машины читает перед прыжком:

-bios none -kernel hello.elf    0x80000000
-bios none -kernel high.elf     0x80000000
-bios hello.elf                 0x80000000
-bios high.elf                  0x80100000

hello.elf собран по 0x80000000, high.elf по 0x80100000. Под -kernel переходник машины не меняется вообще: какой файл ни дай, управление уйдёт туда, куда зашито. А -bios этот переходник переписал адресом из заголовка.

Похоже, в этом и вся разница. -bios и loader с cpu-num=0 дают файлу распорядиться тем, откуда машина стартует, а на кристалле адрес сброса зашит в железо. Стенд у меня стоит на -bios none как раз ради этого, и читать заголовок там просто некому.

Досюда всё про QEMU, а на плате читать заголовок обычно уже некому: objcopy -O binary срезает их физически, у меня из 5824 байт ELF остаётся 472 байта прошивки, и сигнатуры \x7fELF в ней нет. Правда, -O ihex и -O srec точку входа всё-таки несут, отдельной записью (:040000058000001C5B и S7058000001C5E), так что зависит от формата. А дальше от того, кто грузит: U-Boot в bootelf прыгает именно на e_entry (lib/elf.c, return ehdr->e_entry), а вот load_image в OpenOCD только кладёт байты, адрес там задаётся отдельной resume. Похоже, поле читает не машина, а загрузчик, и вопрос всегда в том, есть ли он. Своей платы у меня нет, на железе не проверял.

Как дотянусь до железок перепроверю(но это не точно)

Прочитал обе части. Ухватил не всё с первого раза.

Но одну вещь вроде понял. У меня в коде к этой самой статье есть хвостовой вызов: print_dec не зовёт print_str, а прыгает в неё, отдав свой кадр. Я это сделал просто чтобы не заводить лишний кадр. А у вас во второй части выходит, что если так поступить со всеми вызовами, то возвращаться станет некуда и стек возвратов не нужен вовсе. Не думал, что моя мелкая экономия это край чего-то другого.
Поправьте, если я не туда понял.

У меня в конце статьи как раз вывод, что стек это не железо, а договорённость: sp оказался обычным x2, и держится всё на том, что её соблюдают. Ваши примеры это то же самое с другой стороны.

Про 6502 не думал совсем. 256 байт на фиксированной странице, и не подвинуть.

А правильно понимаю, что ответ про глубокую рекурсию уже в первом вашем примере? Цепочка областей сохранения это ведь кадры, которым не надо лежать одним куском, и тогда глубина упирается не в отведённый заранее размер.

Для работы, наверное, так и правильно. Но если рекурсию убрать, то и стек по сути не нужен, а мне хотелось повозиться именно с ним и посмотреть, где у него пределы.

Благодарю, возьму на заметку.

О, спасибо. Я про FPGA толком ничего не знаю, даже не думал, что туда можно залезть с такой задачей. Пойду почитаю, с чего там вообще начинают. Если разберусь и раздобуду железку, обязательно попробую что-нибудь на ней сделать.

За нативный эмулятор посмеялся, спасибо. Нет уж, мне и одного маленького патча хватило, чтобы зауважать тех, кто пишет это целиком.
Про физику вы точно подметили. Я добавил в модель всего одну вещь, время передачи байта, и мой собственный код тут же рассыпался. Сколько там ещё таких мелочей на настоящей плате, страшно представить.

Вариант добавил.
Спасибо за мысль. У вас забыть ожидание вывода нельзя физически: процессор там сам отсчитывает такты и сам работает устройством, ожидание и есть вся программа. Моя ошибка стала возможной только потому, что ждёт кто-то вместо меня. А эмулятор спрятал и это.
Выходит, чем удобнее железо, тем проще написать код который на нем же и развалится.

Проверил, вы правы.
Написал программу на 300 тысяч записей, которая считает, сколько раз передатчик оказался занят.
С выводом в файл ноль раз. С медленным TCP-приёмником счётчик сразу упирается в потолок. THRE снимается нормально, просто у меня на другом конце была консоль, которая не тормозит никогда.
FIFO для этого даже не нужен, дело в tsr_retry. Проброс настроек в реальный порт тоже нашёл, всё так.
Статья ещё не опубликована, поправку внесу сразу. Спасибо.

Спасибо, вы попали в больное место.
Полез проверять и упёрся в неожиданное: мой "стенд" эту ошибку показать не может в принципе.
QEMU отдаёт байты так быстро, как получится, скорость линии не моделирует, и THRE у него не снимается никогда.
То есть проверить себя мне было нечем, даже если бы я задумался.
Тема оказалась заметно больше комментария.
Разбираю её во второй статье: собираю стенд, на котором ошибку видно.
Спасибо за наводку, сам бы прошёл мимо.

В лоб не получилось, неэкспортируемая.

XD Именно так! Вопрос тоже решаемый но не такой критичный как осознание в моменте что ты потерял ЭЦП. Тут больше эмоциональная составляющая. Кто сталкивался, меня поймет.

Спасибо, очень подробно и понятно. Многим будет полезно. Содержимое ключа предпочту оставить на ключе. Ну а так тоже как альтернатива.

Спасибо, не увидел. Протестирую если будет время.

Опишите поподробнее "veracrypt и Yandex Disk". Не совсем понятно как можно решить данную задачу с помощью этих инструментов?

1

Информация

В рейтинге
186-й
Откуда
Москва, Москва и Московская обл., Россия
Дата рождения
Зарегистрирован
Активность