Всем начинающим программистам всегда говорят о важности правильного оформления сообщений об ошибках.
Всегда говорят, что если программа не смогла что-то сделать, то она должна четко и недвусмысленно сказать, почему это произошло.
Они говорят о важности контроля возвращаемого значения вызываемых функций.
В то же время даже компиляторы научились выдавать предупреждения, если значение, возвращаемое определенными функциями, игнорируется.
Надо сказать, что современные программисты осознали важность обработки ошибок.
Порой это приводит к интересным инцидентам, как на КДПВ (снято Здесь ).
В реальной жизни я несколько раз сталкивался с подобными странными диагностическими сообщениями.
О последнем случае и методах преодоления такого диагноза я хочу поговорить.
Если вам интересно, добро пожаловать под кат. Опытные программисты, вероятно, не откроют для себя ничего нового, но пофилософствовать о разработке ПО они точно смогут. Для тех, у кого «много букв»
- Важно правильно обрабатывать значения, возвращаемые функцией read().
- Не забудьте проверить актуальность используемого вами программного обеспечения (особенно OpenSource).
- многофункциональные программы-комбайны в большинстве случаев одинаково неудобны для решения каких-либо действительных задач.
С другой стороны, производить " москитный флот "не хорошая идея
Больше картинок не будет. Мы спустимся на уровень системной консоли Linux и будем жить там.
В то же время мы будем радоваться.
Потому что проект, с которым нам предстоит работать, достаточно известен.
Проект с открытым исходным кодом, поддерживаемый компанией Разработка программного обеспечения DENX .
Поэтому будем радоваться, что у нас есть консоль, системное окружение и вообще жизнь кипит. Потому что при работе с этим проектом, как правило, ничего такого нет — непрерывные области памяти, пересылка байтов из одного места в другое и ожидание готовности периферии.
Но, кстати, эта часть уже пройдена и есть вполне рабочий загрузчик железа.
Пришло время поработать над украшениями, которые позволят программистам приложений как-то влиять на процесс загрузки системы.
Никаких признаков проблем нет. Проблема давно решена и активно используется такими популярными проектами, как OpenWRT и многие другие, чуть менее известные.
Суть очень проста.
U-Boot корректирует свое поведение в зависимости от переменных среды.
Переменные среды между перезагрузками могут быть сохранены в энергонезависимой памяти.
Утилиты командной строки fw_printenv И fw_setenv позволяют отображать и соответствующим образом изменять их значения непосредственно из Linux. Все.
В принципе, больше ничего и не требуется.
Инструкцию, как всегда, будем читать «когда дым рассеется».
И откуда здесь дым? Весь дым вышел при адаптации загрузчика под эту плату.
Поэтому смело набираем команду «fw_printenv», ведь сломать она ничего уж точно не сможет.
Ну, как и ожидалось.localhost ~ # fw_printenv Cannot open /dev/mtd1: No such file or directory localhost ~ # fw_printenv --help Usage: fw_printenv [OPTIONS].
[VARIABLE].
Print variables from U-Boot environment -h, --help print this help. -v, --version display version -c, --config configuration file, default:/etc/fw_env.config -n, --noheader do not repeat variable name in output -l, --lock lock node, default:/var/lock
Конечно.
Мы не указали, где именно хранятся переменные среды.
А «быстрая помощь» явно указывает на то, что указать ее в командной строке не получится.
Нам нужно отредактировать файл конфигурации /etc/fw_env.config .
Формат файла довольно простой и интуитивно понятный.
Чтобы не создавать трудностей себе (и окружающим), я разместил переменные среды U-Boot в самом доступном месте, которое только можно придумать.
В частности, в файле uboot.env первого раздела основного диска, отформатированном с использованием действительно переносимой файловой системы vfat (она же FAT-32).
И проверил.
Из консоли U-Boot переменные сохраняются в файл и считываются из него при запуске.
Красота.
Остается только дать возможность редактировать их из Linux. Раздел с файлом uboot.env, а также ядром, файлом дерева устройств и некоторым дополнительным содержимым, критичным для работы системы, вполне логично монтируется в /boot. Поэтому я без всякого сомнения комментирую строки 11 и 12 (/dev/mtd1 и /dev/mdt2 соответственно) и удаляю комментарий из строки 30 (/boot/uboot.env) в файле конфигурации.
# VFAT example
/boot/uboot.env 0x0000 0x4000
Все.
Кажется, все подготовительные операции завершены.
Возьми два.
localhost ~ # fw_printenv
Read error on /boot/uboot.env: Success
Ну здравствуй, КДПВ.
Первая разумная мысль, которая приходит любому пользователю Linux в такой ситуации – а что насчет прав? Однако наш лозунг «Слабоумие и мужество» — мы работаем как корень.
Логично.
Чего стоит опасаться человеку, который делает загрузчик для железки и имеет максимально физический (с помощью паяльника) доступ к плате? Или может быть файл просто не существует? Забыли сказать «saveenv» в консоли U-Boot? Давай проверим.
localhost ~ # ls -l /boot/uboot.env
-rwxr-xr-x 1 root root 8192 Dec 2 13:22 /boot/uboot.env
Нет, он есть.
И ее даже может прочитать весь мир (ох как нехорошо).
Интересно, а что, если его не существует? localhost ~ # mv /boot/uboot.env /boot/uboot.env.bak
localhost ~ # fw_printenv
Cannot open /boot/uboot.env: No such file or directory
localhost ~ # mv /boot/uboot.env.bak /boot/uboot.env
Логично.
Здесь все правильно.
Ладно, мы глубоко вздохнули и.
Это наш кактус, его надо погрызть.
Хорошо, что есть исходники.
Нам нужно посмотреть, что там происходит. Может быть какие-то мысли появятся? Мы находим это очень быстро строка 950 в файле Tools/env/fw_env.c : lseek(fd, blockstart + block_seek, SEEK_SET);
rc = read(fd, buf + processed, readlen);
if (rc == -1) {
fprintf(stderr, "Read error on %s: %s\n",
DEVNAME(dev), strerror(errno));
return -1;
}
if (rc != readlen) {
fprintf(stderr,
"Read error on %s: Attempted to read %zd bytes but got %d\n",
DEVNAME(dev), readlen, rc);
return -1;
}
Нет. Это вполне классическая оболочка для функции read().
Практически прямо из учебника.
И судя по поведению итоговой программы, можно не сомневаться, что read() возвращает -1, но errno остаётся нулем.
Ждать.
Что это за скрежет? Ох, мой мозг начал двигаться.
Ладно.
Ну, ты можешь это прочитать прочитайте руководство ? Нет, ерунда.
Вроде всё про прочитанное прочитано и перечитано.
Все мыслимые и немыслимые варианты ошибок с функцией read() давно известны.
Так не должно быть.
Что мы делаем дальше? Правильно, раз уж исходный код не дает ответа, пусть его дает сама система.
localhost ~ # strace fw_printenv
execve("/usr/bin/fw_printenv", ["fw_printenv"], 0x7ebf2400 /* 28 vars */) = 0
brk(NULL) = 0x2118000
uname({sysname="Linux", nodename="localhost", .
}) = 0 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3 fstat64(3, {st_mode=S_IFREG|0644, st_size=42265, .
}) = 0 mmap2(NULL, 42265, PROT_READ, MAP_PRIVATE, 3, 0) = 0x76f14000 close(3) = 0 openat(AT_FDCWD, "/lib/libc.so.6", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3 read(3, "\177ELF\1\1\1\3\0\0\0\0\0\0\0\0\3\0(\0\1\0\0\0\f~\1\0004\0\0\0".
, 512) = 512 fstat64(3, {st_mode=S_IFREG|0755, st_size=1286448, .
}) = 0 mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x76f12000 mmap2(NULL, 1356160, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x76da1000 mprotect(0x76ed7000, 65536, PROT_NONE) = 0 mmap2(0x76ee7000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x136000) = 0x76ee7000 mmap2(0x76eea000, 8576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x76eea000 close(3) = 0 set_tls(0x76f12ca0) = 0 mprotect(0x76ee7000, 8192, PROT_READ) = 0 mprotect(0x4a9000, 4096, PROT_READ) = 0 mprotect(0x76f1f000, 4096, PROT_READ) = 0 munmap(0x76f14000, 42265) = 0 openat(AT_FDCWD, "/var/lock/fw_printenv.lock", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3 flock(3, LOCK_EX) = 0 brk(NULL) = 0x2118000 brk(0x2139000) = 0x2139000 openat(AT_FDCWD, "/etc/fw_env.config", O_RDONLY) = 4 fstat64(4, {st_mode=S_IFREG|0644, st_size=1342, .
}) = 0 read(4, "# Configuration file for fw_(pri".
, 4096) = 1342 read(4, "", 4096) = 0 close(4) = 0 openat(AT_FDCWD, "/boot/uboot.env", O_RDONLY) = 4 fstat64(4, {st_mode=S_IFREG|0755, st_size=8192, .
}) = 0 close(4) = 0 openat(AT_FDCWD, "/boot/uboot.env", O_RDONLY) = 4 _llseek(4, 0, [0], SEEK_SET) = 0 read(4, "n.'\202__INF0__=Ravion-V2 I.MX6 CPU".
, 16384) = 8192 write(2, "Read error on /boot/uboot.env: S".
, 39Read error on /boot/uboot.env: Success
) = 39
close(4) = 0
flock(3, LOCK_UN) = 0
close(3) = 0
exit_group(1) = ?
+++ exited with 1 +++
localhost ~ #
Я люблю Линукс.
Ох, какая красота.
Все сразу встало на свои места.
Ладно, согласен - не все.
Но уже что-то.
Самое интересное здесь: openat(AT_FDCWD, "/boot/uboot.env", O_RDONLY) = 4
_llseek(4, 0, [0], SEEK_SET) = 0
read(4, "n.'\202__INF0__=Ravion-V2 I.MX6 CPU".
, 16384) = 8192 write(2, "Read error on /boot/uboot.env: S".
, 39Read error on /boot/uboot.env: Success
) = 39
Пытаемся прочитать 16384 (16К), но можем прочитать только 8192 (8К).
Вот и все.
Бинго.
Давайте поднимемся выше и посмотрим на размер файла.
Да, это действительно 8192 байта.
Давайте поднимемся еще выше и посмотрим на строчку в конфиге.
Смещение 0, длина 0x4000 или 16384. Исправьте до 0x2000. # VFAT example
/boot/uboot.env 0x0000 0x2000
Да, черт возьми, я очень стар.
По моему мнению, в U-Boot достаточно места для переменных окружения и килобайта.
Это также пустая трата драгоценной памяти.
Что бы вы хотели.
Уроженец Санкт-Петербурга.
Нас с детства учили, что хлеб (ресурсы) надо беречь.
А выбросить его – значит не дорожить памятью о тех, кто погиб из-за ее отсутствия во время блокады.
И да, мы такие.
Спасибо настоящим ветеранам, спасибо музеям города.
Которые, несмотря ни на что, сохранят память о тех страшных временах .
Я надеюсь, что мои дети тоже научатся этому.
Итак — о переменных окружения для U-Boot. Ну два килобайта.
Ну да ладно - четыре.
Где больше? Что там можно написать в таких количествах (и главное, зачем)? Поэтому действительно был момент, когда выделенные по умолчанию 16К были урезаны до 8. Я тоже подумал - где столько? Ладно, оставим лирику - давайте проверим.
localhost ~ # fw_printenv
__INF0__=Ravion-V2 I.MX6 CPU Module BSP package
__INF1__=Created: Alex A. Mihaylov AKA MinimumLaw, [email protected]
[…]
boot_os=1
localhost ~ #
Работает. И даже fw_setenv работает. localhost ~ # fw_setenv boot_os 0; fw_printenv boot_os
boot_os=0
Могу ли я положить этому конец? Я думаю нет. Остается один важный вопрос, который уважающий себя программист не имеет права игнорировать.
Как вы думаете, в чем вопрос? Вы думаете правильно.
Если вы посмотрите на код, который находится в репозитории U-Boot, вы легко увидите, что эту ситуацию следует обработать правильно.
Я специально не вырезал этот кусок выше.
Более того, strace совершенно честно и открыто заявляет, что read возвращает значение 8192. Так почему же мы оказываемся в ветке с ошибкой чтения? В конце концов, 8192 не может равняться -1. Давайте разберемся.
Первая мысль, которая приходит в голову — подождите, но Das U-Boot — это динамично развивающийся проект. Возможно, мы смотрим репозиторий с последней версией загрузчика.
Но та часть, которую мы используем, не обязательно должна быть последней.
Это часть пользовательской среды операционной системы.
Это я адаптирую последнюю версию загрузчика, чтобы чувствовать пульс проекта.
А авторы сборок прикладного ПО скорее выступают за стабильность.
Поэтому, вероятно, оно не будет последним.
Давай проверим.
localhost ~ # fw_printenv --version
Compiled with U-Boot 2019.10
localhost ~ #
Ага! А у меня в работе последняя стабильная (2020.10).
Разница составляет год. Огромная дистанция для динамично развивающегося OpenSource-проекта.
lseek(fd, blockstart + block_seek, SEEK_SET);
rc = read(fd, buf + processed, readlen);
if (rc != readlen) {
fprintf(stderr, "Read error on %s: %s\n",
DEVNAME(dev), strerror(errno));
return -1;
}
Ну да.
Это верно.
Все уже исправлено.
Это позор.
Это была такая красивая ошибка.
Ладно, в нашей жизни еще есть ошибки.
Просто успейте во всем разобраться.
Но это не все.
Давайте посмотрим на файл «uboot.env».
localhost ~ # hexdump -C /boot/uboot.env
00000000 0a 43 62 eb 5f 5f 49 4e 46 30 5f 5f 3d 52 61 76 |.
Cb.__INF0__=Rav| 00000010 69 6f 6e 2d 56 32 20 49 2e 4d 58 36 20 43 50 55 |ion-V2 I.MX6 CPU| 00000020 20 4d 6f 64 75 6c 65 20 42 53 50 20 70 61 63 6b | Module BSP pack| 00000030 61 67 65 00 5f 5f 49 4e 46 31 5f 5f 3d 43 72 65 |age.__INF1__=Cre| [.
] 00000720 3d 71 70 00 76 65 6e 64 6f 72 3d 72 61 76 69 6f |=qp.vendor=ravio| 00000730 6e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |n.| 00000740 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |.
|
*
00002000
localhost ~ #
Факт вполне очевиден — оценка размера блока, достаточного для хранения переменных окружения, которую я дал выше, вполне справедлива.
На данный момент использовано 1837 байт (0x7031 – 4), а формат блока довольно прост. Первые 4 байта — это CRC32, а затем переменные, разделенные нулями, в формате переменная=значение.
Другими словами, поведение утилиты по-прежнему вызывает вопросы.
Ну напишет, что размер файла меньше ожидаемого и закончится ошибкой.
Но это не правда.
В него поместятся все значимые и важные данные (даже двухкилобайтные!).
Может быть, все же стоит исправить? К сожалению нет. И причина этого довольно банальна.
Переменные в U-Boot могут храниться в разных местах.
Файл в разделе vfat - самое сладкое место.
Вот почему я выбрал его.
Но у OpenWRT нет таких удобных запоминающих устройств.
Есть SPI-вспышка.
И целый сектор отведен под экологические переменные.
Но и здесь все может быть не так уж и плохо.
Весь сектор необходимо стереть.
Можно писать частями.
Проблема заключается в системах, использующих флэш-память данных или некоторые варианты дисков raw-NAND. Те.
с теми системами, которым помимо данных необходима еще и управляющая информация для контроля целостности и работоспособности.
Поэтому они обязаны написать весь блок.
Оказывается, это интересная альтернатива.
Либо мы делаем разные утилиты для работы с переменными окружения для разных дисков, либо.
Делаем это одинаково неудобно для всех.
Посмотрите код утилиты.
Посмотрите на формат сохраняемых данных.
К сожалению, так происходит почти всегда.
Мы хотели, чтобы все хорошо провели время.
Конечный результат был одинаково плох для всех.
Однако решение полностью обеспечивает свой функционал.
Пусть так и останется.
Вот и неожиданно получилось это легкое пятничное чтение.
Было бы интересно посмотреть, сколько времени в целом продлится эта ошибка, но.
Не так уж интересно тратить на нее время.
Как говаривал классик: «Сказка – ложь, да в ней намек.
Урок добрым молодцам.
" Спасибо, что дочитали.
P.S. Я пользуюсь этой возможностью, чтобы поздороваться КодРаш Еще раз спасибо за приглашение на Хабр.
И да, мне всегда хочется писать о серьезных вещах — о компиляторах, о безопасном программировании непосредственно на железе.
А сил мне хватает только на легкое пятничное чтение.
Ладно, будем считать, что начало положено.
Большое путешествие всегда начинается с маленького шага.
Теги: #linux #разработка Linux #Процессоры #Промышленное программирование #консоль #u-boot #embedded
-
Процесс Квантового Туннелирования
19 Oct, 24