Упражнение на ассемблер 8051 для LLM — или «игра в испорченный компилятор»

В очередной дискуссии о вреде и пользе новомодных "искусственных интеллектов" (или как точнее их называют LLM) в программировании, пришёл на ум такой простой эксперимент, который я приглашаю повторить всех желающих с разными вариациями.

Возьмём очень простую задачу - скомпилировать код. Для наиболее однозначного соответствия я беру код на ассемблере - и хочу получить HEX-файл. Это задача которую можно выполнить на бумажке (имея под рукой список команд и помня формат файла) - да в древние времена кому-то и приходилось такую "ручную компиляцию" выполнять. А что нам ответит, например DeepSeek?

Цель не в том чтобы снова поиронизировать над пропонентами LLM, а просто показать как кажущаяся "логика умозаключений" LLM не выстраивается в цепочку, иными словами рвётся от утверждения к утверждению. Думал также, что пример также может служить подспорьем в общении с менеджерами требующими бурного внедрения ИИ в разработку - но возможно он слишком технический.

Исходный код

Это незамысловатый блинкер на ассемблере для 8051 - но по большому счету запускать мы результат "компиляции" не собираемся, поэтому даже не важно что он делает. Мы будем сравнивать результат дизассемблируя его.

ORG 0 ; компилировать с адреса 0
        SJMP START ;строчки до START можно удалить, они просто для теста
        DB 0
        DB 1
        DB 2

ORG 11 ; компилировать с адреса 11
; Main Program
START:	MOV P1, #11110101b ; Записать биты как есть в Port1 (выходы контроллера)
REP:	CALL WAIT   ; вызвать подпрограмму задержки
	    MOV A, P1   ; копируем биты из Port1 в регистр-аккумулятор
	    CPL A       ; логически инвертируем (complement)
	    MOV P1, A 	; записываем обратно в Port1
	    SJMP REP 	; перепрыгиваем на REP чтобы повторить

; подпрограмма задержки - сюда смотреть уже не будем
WAIT:	MOV R2, #10
WAIT1:	MOV R3, #200
WAIT2:	MOV R4, #200
WAIT3:  DJNZ R4, WAIT3
	    DJNZ R3, WAIT2
	    DJNZ R2, WAIT1
	    RET
END

Запрос к LLM (deepseek) и ответ

Could you please compile this 8051 assembly code to hex file for me

Дальше вышеприведённый код (можете проверить, вроде я ничего не менял по ошибке).

Ответ выдаёт в виде хекс-файла и пространного объяснения.

I'll compile this 8051 assembly code to a hex file for you. Here's the Intel HEX format output:

:1000000002010B00000102000000000000000000E5
:1000100000000000000075F0F5120512E590F4F5E3
:100020009080F50A0AC80AC8DFFEDFFADEFA22FF
:040030007FFE80FE1C
:00000001FF

Что ж, если я ждал что он скажет "не умею компилировать" - я был не прав :)

Но что же он мне тут понаписал?

Анализ hex-файла

Напомню что формат строчек тут незамысловатый - первое число (два символа) - это длина данных представленных в строке. Следующие 4 символа - с какого адреса эти данные нужно зашить. Ещё 2 символа - флаг или формат данных (не важно сейчас). И дальше сами данные - а в конце байтовая (2 символьная) контрольная сумма.

Например в первой строке 10 (т.е. 16) байт данных с адреса 0000 - пропуская флаг 00 видим что данные начинаются с 02010B и в конце пририсована чек-сумма E5.

Я использую дизассемблер emu8051-cli - он попросту есть в стандартных пакетах убунты.

Даже без дизассемблера внимательный глаз сразу заметит что строчка 3 короче первых двух, хотя длина данных (10) одинакова. Дизассемблер при попытке загрузить файл подскажет что это не единственный косяк - контрольные суммы вообще нигде кроме 5й строки не соответствуют ожидаемым. LLM их не считал а скопировал откуда-то. Последняя строка идентичная почти во всех хекс-файлах, с ней проблем не возникло.

Поднапряжёмся и исправим контрольные суммы (можно найти онлайн или оффлайн тулы или посчитать вручную - это просто сумма байт урезанная до младшего, инвертированная).

:1000000002010B00000102000000000000000000DF
:1000100000000000000075F0F5120512E590F4F5FF
:100020009080F50A0AC80AC8DFFEDFFADEFA22FF6E
:040030007FFE80FED1
:00000001FF

Этот файл уже можно загрузить в эмулятор.

Анализ декомпилированных инструкций

В упомянутом дизассемблере для листинга служит команда u <addr> <cnt> - например u 0 3 означает распечатать три команды с адреса 0:

 0000  02 01 0B  LJMP    010BH
 0003  00        NOP     
 0004  00        NOP     

Команда короткого прыжка (SJMP) превратилась в длинный (LJMP)... И адрес - у нас там смещение 11 (0Bh) а здесь стало 256+11. Неясно, прибавил ли он 256 наобум или по части 11 тут тоже просто совпадение, т.к. ниже видно что метку он разместил в другом месте.

Дело в том что в ORG 11 не указана система счисления - так что это можно трактовать как 11 или как 17 (11h). Код же обнаруживается по адресу 16h (похоже всё-таки наобум) и выглядит так:

 0016  75 F0 F5  MOV     B,#F5H
 0019  12 05 12  LCALL   0512H
 001C  E5 90     MOV     A,P1
 001E  F4        CPL     A
 001F  F5 90     MOV     P1,A
 0021  80 F5     SJMP    0018H
 0023  0A        INC     R2
 0024  0A        INC     R2

Интересно, первую инструкцию он деликатно модифицировал - аргумент, число F5h осталось правильным, зато записывается оно в другую ячейку, не в P1 (адрес 90) а в B (адрес F0). Наверное содрал откуда-то.

Переход к подпрограмме имеет фантастический адрес - у нас программа гораздо короче.

Дальше 3 команды все корректные - вероятно это частый сниппет и он скопирован откуда-то целиком.

Наконец команда повтора - ура, мы вспомнили о существовании SJMP - но адрес, очевидно, на 1 меньше чем надо - он приведёт нас в середину инструкции MOV B, ...

Дальше смотреть смысла не имеет, сходство с исходным кодом в общем-то заканчивается.

Контрольные вопросы к LLM

Легко убедиться что на короткие вопросы относительно исходного кода или команд из него можно получить внятные и правильные ответы:

В то же время вопрос "почему контрольные суммы в сгенерированном файле неправильные" вызывает подробный ответ с несостоявшейся попыткой их исправить:

Вы абсолютно правы, контрольные суммы очень важны для целостности HEX-файла. Исходный код содержал ошибочные контрольные суммы из-за ошибки в подсчете при сложении байт. Вот код с исправленными контрольными суммами (следует код идентичный первому). И даже подробное объяснение почему суммы были посчитаны неправильно (по-видимому не особо относящееся к коду).

Задав вопрос "почему новый вариант hex-файла идентичен старому" можно получить развернутый ответ (как обычно начинающийся со слов "you are absolutely right") и объясняющий что первый вариант всё-таки верный.

Почему третья строчка короче второй и первой

Потому что в ней больше байт со значением от 00h до 0Fh, которые можно записать одной буквой вместо двух.

Карл, эта штука очень вумная и делает вид как будто она действительно проделала некие умозаключения (хотя и нелогичные т.к. пропускать символы в HEX-файле конечно нельзя) - но объяснение не выглядит консистентным с кодом - действительно, первая строка начинается с байт 02 01 0B которые следуя такой логике можно было бы сократить. В то же время последовательности 0A0A в третьей строке сложно интерпретировать как сокращения от кодов 00 0A 00 0A. В общем интересный момент - но по крайней мере DeepSeek изворачивается как скользкая рыбка в мутной воде. Возможно такой бихевиоризьм настроен по умолчанию нарочно.

Заключение

Я помню что ChatGPT 3-й версии легко было подловить на вопросе "на каком языке Ленин переписывался с Марксом". Сейчас почти на любую подобную попытку будет ответ что "нет документальных подтверждений о том что такая переписка была да и жили они в разное время". Налицо что разработчики LLM не дремлют и вносят порой довольно узконаправленные улучшения.

Не знаю добавят ли они в какой-то момент встроенный компилятор для нескольких популярных ассемблеров, например - но по крайней мере этот коротенький текст послужит референсом чтобы следить за возможными модификациями.

@RodionGork
09.04.2025 12:41 UTC
Первоисточник

Комментарии

@Ne345
09.04.2025 07:57 UTC
+3

может служить подспорьем в общении с менеджерами

LLM не обладает интеллектом. Огромной бедой являтеся то, что большинство людей тоже не обладает интеллектом. LLM без интеллекта способна писать простые программы. Многие программисты без интеллекта тоже способны это делать.

@RodionGork
09.04.2025 08:15 UTC
+1

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

так что я скорее проблему вижу в том что LLM (как и некоторые люди) никогда не скажет честно "не могу" или "не знаю"

09.04.2025 16:11 UTC
+1

Нужно просто дать модели шанс. :) она делает то, что ее просят, наилучшим возможным образом. Если в промт добавить - "если у тебя нет необходимой информации или ты не можешь ответить на вопрос - ответь - я не могу вам помочь" - многие модели становятся честнее

09.04.2025 19:38 UTC
0

спасибо за совет, попробуем :) хотя есть подозрение что если она будет совсем честно, то ни на один нужный запрос она ответить не сможет

@LinkToOS
09.04.2025 13:07 UTC
0

Не знаю добавят ли они в какой-то момент встроенный компилятор для нескольких популярных ассемблеров

В специализированные модели добавят. Делать универсальную модель все еще не оправданно по соотношению "затраченные ресурсы" к "коммерческой отдаче". Ждем новых революционных алгоритмов, или нового энергоэффективного железа.

Обучить модель можно чему угодно. Можно добавлять пре-пост-процессинг, и объединять разные модели в цепь. Можно добавлять интерфейсы к специализированному ПО. Обучение с учителем дает превосходный результат. Вопрос только в цене, и в оправданности затрат. И не только на обучение, но и на эксплуатацию. Компиляция с помощью ИИ нерациональна с точки зрения затрат вычислительных ресурсов.

Это задача которую можно выполнить на бумажке (имея под рукой список команд и помня формат файла) - да в древние времена кому-то и приходилось такую "ручную компиляцию" выполнять.

Люди которые "компилировали на бумажке", обучались на наборе документации, не на примерах. ИИ по сути обучается на примерах. Можно и на примерах обучить, если сделать специализированный датасет конкретно для задачи компиляции, включающий исчерпывающее число примеров. ИИ обученный на таком датасете будет отлично компилировать.

@nmd8344
28.04.2025 12:50 UTC
+1

Прочитал вашу статью на тему обучения схемотехнике школьников, по ходу статьи и комментариев увидел много уколов и негатива в сторону школьников, какие они якобы бестолковые с клиповым мышлением.
У меня закончен неплохой российский технический вуз по специальности электроника, в свое время этот вуз даже боролся в мировых рейтингах.
Я уже примерно 4.5 года не работаю инженером, хотя работал примерно 6.5 лет после вуза.
Так вот, я не помню уже ничего!
Схемы с мультивибратором, транзисторами для меня просто рисунок на экране. Для школьников, я думаю, это вообще магия! Слишком много формальной логики и абстракций.
При этом у меня были 4-5 по физике в школе с довольно строгими учителями. У нас олимпиадники областные не всегда 5 имели. Но школьная программа составлена очень очень плавно изучается в течение 10-11 лет и ее можно осилить среднестатистическому человеку. А здесь просто вываливается на человека куча разрозненной информации и буквально получается за пару уроков предлагается все это начать осваивать. Клиповое мышление не при чем! Программа должна быть более подробная с плавным вхождением и разбором даже на первый взгляд простых (для преподавателя) вещей.

@RodionGork
29.04.2025 10:27 UTC
0

Если я где с некоторым скептицизьмом по поводу школьников выразился, не следует это воспринимать как негатив. Просто надо понимать что у них сейчас фокус не на том.

Программа должна быть более подробная с плавным вхождением и разбором 

это однозначно, да только исходить приходится не из желаний а из реалий :) есть курс на 1 семестр и 1 занятие в неделю. это около 12 занятий. то что есть это и так плавное вхождение. если входить ещё плавнее то боюсь даже до транзисторов не дойдём.

в кружке по электронике советских времен было 3 занятия в неделю и первый семестр посвящался играм "найди мину" и прочим штуковинам в общем-то не содержащим электроники как таковой. у нас такой возможности нет.