Ошибка: Успех И Что С Этим Делать



Ошибка: успех и что с этим делать

Всем начинающим программистам всегда говорят о важности правильного оформления сообщений об ошибках.

Всегда говорят, что если программа не смогла что-то сделать, то она должна четко и недвусмысленно сказать, почему это произошло.

Они говорят о важности контроля возвращаемого значения вызываемых функций.

В то же время даже компиляторы научились выдавать предупреждения, если значение, возвращаемое определенными функциями, игнорируется.

Надо сказать, что современные программисты осознали важность обработки ошибок.

Порой это приводит к интересным инцидентам, как на КДПВ (снято Здесь ).

В реальной жизни я несколько раз сталкивался с подобными странными диагностическими сообщениями.

О последнем случае и методах преодоления такого диагноза я хочу поговорить.

Если вам интересно, добро пожаловать под кат. Опытные программисты, вероятно, не откроют для себя ничего нового, но пофилософствовать о разработке ПО они точно смогут. Для тех, у кого «много букв»

  • Важно правильно обрабатывать значения, возвращаемые функцией read().

  • Не забудьте проверить актуальность используемого вами программного обеспечения (особенно OpenSource).

  • многофункциональные программы-комбайны в большинстве случаев одинаково неудобны для решения каких-либо действительных задач.

    С другой стороны, производить " москитный флот "не хорошая идея

В общем, у меня печальные новости.

Больше картинок не будет. Мы спустимся на уровень системной консоли Linux и будем жить там.

В то же время мы будем радоваться.

Потому что проект, с которым нам предстоит работать, достаточно известен.

загрузчик U-Boot .

Проект с открытым исходным кодом, поддерживаемый компанией Разработка программного обеспечения 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

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.