Сборка x86: вывод целого числа на консоль после mul (ошибка сегмента)

Я пытаюсь изучить сборку x86. Я использую книгу Assembly Language - Step by Step, Programming With Linux (и я должен сказать, что она очень хороша). На данный момент я многому научился, но я чувствую, что мне также следует бросать себе вызов, чтобы оставаться впереди во многих отношениях, чтобы я мог учиться быстрее, работая (я могу следовать за мной, учиться сверху вниз, но я нахожу это утомительным). медленный).

Итак, я подумал, что было бы неплохо попытаться перемножить два регистра (32-битных) и затем вывести данные на консоль.

Проблема в том, что когда я запускаю программу (я использую NASM, как и в книге, но без отладчика Insight), я получаю ошибку сегментации. Я проделал изрядную часть отладки в gdb с помощью этого небольшого молотка, но по какой-то причине я не могу понять, в чем проблема.

Я хотел бы знать, почему я получаю сообщение об ошибке сегментации и как правильно сделать выговор за эту проблему. Кроме того, если комментарии, которые я сделал в коде, не совпадают с тем, что именно происходит, я был бы признателен, если бы кто-нибудь меня поправил.

Вот мой код до сих пор (он хорошо прокомментирован)

Спасибо.

те кодез

section .data
;TODO

section .bss
valueToPrint: resb 4            ;alloc 4 bytes of data in 'valueToPrint'

section .text

global _start

_mul:
    mov eax, 0x2A ;store 42 in eax
    mov edx, 0x2A ;store 42 in edx
    mul eax
    ret

_safe_exit:
    mov eax, 1  ;initiate 'exit' syscall
    mov ebx, 0  ;exit with error code 0
    int 0x80    ;invoke kernel to do its bidding 

_start:
    nop                         ;used to keep gdb from complaining

    call _mul                       ;multiply the values
    mov [valueToPrint], eax         ;store address of eax in the contents of valueToPrint
    mov eax, 4                      ;specify a system write call - aka syswrite
    mov ebx, 1                      ;direction used to make the syswrite call output to console - i.e. stdout
    mov dword [ecx], valueToPrint   ;store valueToPrint in ecx: ecx represents the syswrite register
    int 0x80                        ;invoke kernel based on the given parameters

    call _safe_exit

Изменить

Кроме того, я использую Arch Linux, если это имеет значение.


person zeboidlund    schedule 29.06.2012    source источник
comment
в вашей подпрограмме _mul вы используете mul eax, которое умножает eax на eax... поскольку вы используете 42 и в eax, и в edx, вы получаете (ну, вы ДОЛЖНЫ получить) правильный ответ, но вы не включает значение в edx. @Adam Rosenfield (ниже) имеет правильный ответ, хотя вы не создаете строку TEXT/ASCII для печати в первую очередь. на самом деле сборка ничего автоматически не делает для вас. Вот почему у вас так много контроля... но вы должны говорить ему, как все делать. Вы не создаете текстовую строку, чтобы указать ecx для печати. (вам также нужна длина)   -  person lornix    schedule 29.06.2012
comment
Кроме того, mul (умножение) умножает значение в eax (ax/rax и т. д.) на все, что указано, и помещает результирующее 64-битное значение в EDX:EAX... поэтому, если вы ищете полный ответ, не забудьте также также используйте значение edx. 42 * 42 = 1764, так что вы далеки от ответа, переносимого в edx, но вы все равно должны знать, что происходит, поэтому вы можете ожидать, что edx изменится и будет использоваться при необходимости.   -  person lornix    schedule 29.06.2012
comment
(черт возьми, я болтлив сегодня вечером!) Маленькие придирки.... ваша подпрограмма '_safe_exit'... поскольку она никогда не возвращается, вы должны вместо этого jmp обратиться к ней. Да, система сбрасывает все, когда программа выходит... но опять же, вы должны знать, как все работает. call _save_exit отлично работает, но для меня он оставляет значение в стеке. Придирчивые вещи. Мои извинения. Я начал сборку на машине 16K еще когда... Память была дорогой, а код писался туго. Я до сих пор так думаю, даже с 8G RAM. Как правило, меньший работает быстрее.   -  person lornix    schedule 29.06.2012
comment
Круто, Лорникс. Спасибо. Как получается, что eax умножается на edx?   -  person zeboidlund    schedule 29.06.2012
comment
mul edx. Прошу прощения за придирки... сборка - это круто, но надо уметь жонглировать всем, так как на самом деле это совсем вам не поможет, а незнание того, как вещи взаимодействуют, вас укусит.   -  person lornix    schedule 29.06.2012
comment
Спасибо! И не беспокойтесь, я действительно ценю критику. В конце концов, я здесь, чтобы учиться :) кстати, если вы заинтересованы в дальнейшем обсуждении низкоуровневого программирования, встречайтесь здесь: chat.stackoverflow.com/rooms/13202/low-level-programming   -  person zeboidlund    schedule 29.06.2012


Ответы (2)


Эта строка вызывает ошибку сегментации:

mov dword [ecx], valueToPrint

Вы говорите ему хранить valueToPrint в ячейке памяти по адресу ecx. Вы никогда не инициализируете ecx (ядро, вероятно, инициализирует его 0 при запуске программы для вас), поэтому, когда вы разыменовываете его, вы получите доступ к недопустимой ячейке памяти.

Системный вызов write(2) принимает 3 параметра: номер файлового дескриптора в регистре ebx, указатель на строка для записи в ecx и количество байтов для записи в edx. Итак, если вы хотите просто распечатать необработанные двоичные данные результата, вы можете передать адрес valueToPrint и сказать, чтобы он печатал 4 байта с этого адреса. В этом случае valueToPrint равно 1764 (0x6e4 в шестнадцатеричном формате), поэтому этот код выведет 4 байта e4 06 00 00 на x86, что является прямым порядком байтов:

mov [valueToPrint], eax   ; store the result into memory
mov eax, 4                ; system call #4 = sys_write
mov ebx, 1                ; file descriptor 1 = stdout
mov ecx, valueToPrint     ; store *address* of valueToPrint into ecx
mov edx, 4                ; write out 4 bytes of data
int 0x80                  ; syscall
person Adam Rosenfield    schedule 29.06.2012
comment
Прежде всего, я хотел бы сказать спасибо за разъяснения. Однако мне было любопытно, насколько сложно будет напечатать целое число в базе 10? - person zeboidlund; 29.06.2012
comment
Чтобы распечатать его в базе 10, вам нужно зарезервировать массив символов, достаточно большой для хранения вывода (не менее 11 байтов для 32-битного целого числа со знаком). Затем несколько раз разделите число на 10, пока не достигнете 0, сохранив оставшиеся плюс 48 в символе в массиве (48 — это ASCII '0'), двигаясь справа налево. Наконец, передайте адрес этого буфера в sys_write() и следите за тем, сколько байтов вы печатаете. Или, если вы хотите все это пропустить, просто позвоните по номеру printf(3). - person Adam Rosenfield; 29.06.2012

РЕДАКТИРОВАТЬ! (забыл ЦМП!)

Чтобы вывести значение в базе 10...

; assuming value is in EAX (only)
.loop1:
    div   10      ; divide by 10, leave result in eax, REMAINDER in edx
    push  eax     ; save value
    mov   eax,edx
    or    eax,0x30 ; convert 0-9 to '0'-'9' (ascii 0x30='0')
    call  display_a_char ; (you write this!)
    pop   eax
    or    eax,eax ; set flags (edit!)
    jnz   .loop1
; all done, maybe put a \n return or something here
person lornix    schedule 29.06.2012
comment
это случайно ... так что это ДОЛЖНО работать нормально, только мелкие проблемы - person lornix; 29.06.2012
comment
в стороне... измените одну строку, добавьте две строки... и это довольно легко преобразует вывод в ЛЮБУЮ базу, 2-36. Ура! - person lornix; 29.06.2012