Ошибка сегментации с переменной в РАЗДЕЛЕ .DATA - PullRequest
0 голосов
/ 10 января 2019

Я пытаюсь научить насм. Я хочу сделать программу, которая печатает «Привет, мир». n раз (в данном случае 10). Я пытаюсь сохранить значение регистра цикла в константе, чтобы оно не изменялось при выполнении тела цикла. Когда я пытаюсь сделать это, я получаю ошибку ошибки сегментации. Я не уверен, почему это происходит.

Мой код:

SECTION .DATA
    print_str:          db 'Hello, world.', 10
    print_str_len:      equ $-print_str

    limit:              equ 10
    step:               dw 1

SECTION .TEXT
    GLOBAL _start 

_start:
    mov eax, 4              ; 'write' system call = 4
    mov ebx, 1              ; file descriptor 1 = STDOUT
    mov ecx, print_str      ; string to write
    mov edx, print_str_len  ; length of string to write
    int 80h                 ; call the kernel

    mov eax, [step]         ; moves the step value to eax
    inc eax                 ; Increment
    mov [step], eax         ; moves the eax value to step
    cmp eax, limit          ; Compare sil to the limit
    jle _start              ; Loop while less or equal

exit:
    mov eax, 1              ; 'exit' system call
    mov ebx, 0              ; exit with error code 0
    int 80h                 ; call the kernel

Результат:

Hello, world.
Segmentation fault (core dumped)

cmd:

nasm -f elf64 file.asm -o file.o
ld file.o -o file
./file

1 Ответ

0 голосов
/ 11 января 2019

section .DATA является прямой причиной аварии. Нижний регистр section .data является специальным и связан как отображение (чтение) (частное) отображения исполняемого файла.

Верхний регистр .DATA не является специальным для компоновщика и заканчивается как часть текстового сегмента, отображенного для чтения + exec без разрешения на запись. ( В чем отличие раздела и сегмент в формате файла ELF ).

Верхний регистр .TEXT также странен: по умолчанию objdump -drwC -Mintel только разбирает секцию .text (чтобы не разбирать данные, как если бы это был код), поэтому он показывает пустой вывод для вашего исполняемого файла.


После запуска программы в GDB (gdb ./foo, starti) я посмотрел карту памяти процесса из другой оболочки.

$ cat /proc/11343/maps
00400000-00401000 r-xp 00000000 00:31 110651257                          /tmp/foo
7ffff7ffa000-7ffff7ffd000 r--p 00000000 00:00 0                          [vvar]
7ffff7ffd000-7ffff7fff000 r-xp 00000000 00:00 0                          [vdso]
7ffffffde000-7ffffffff000 rwxp 00000000 00:00 0                          [stack]

Как видите, кроме специальных сопоставлений VDSO и стека, существует только одно сопоставление с файловой поддержкой, и у него есть только разрешение на чтение + exec.

Один шаг внутри GDB, загрузка mov eax,DWORD PTR ds:0x400086 завершается успешно, но mov DWORD PTR ds:0x400086,eax сохраняет ошибки . (См. Нижнюю часть x86 вики для получения советов по GDB asm.)

Начиная с readelf -a foo, мы можем видеть заголовки программ ELF, которые сообщают загрузчику программ ОС, как отобразить его в память:

$ readelf -a foo   # broken version
  ...
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000000bf 0x00000000000000bf  R      0x200000

 Section to Segment mapping:
  Segment Sections...
   00     .DATA .TEXT 

Обратите внимание, как .DATA и .TEXT находятся в одном сегменте. Это то, что вам нужно для section .rodata (стандартное имя раздела, в которое вы должны помещать постоянные данные только для чтения, например вашу строку), но оно не будет работать для изменяемых глобальных переменных.

После исправления asm для использования section .data и .text, readelf показывает нам:

$ readelf -a foo    # fixed version
  ...
Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000000e7 0x00000000000000e7  R E    0x200000
  LOAD           0x00000000000000e8 0x00000000006000e8 0x00000000006000e8
                 0x0000000000000010 0x0000000000000010  RW     0x200000

 Section to Segment mapping:
  Segment Sections...
   00     .text 
   01     .data 

Обратите внимание, что сегмент 00 - это R + E без W, и там есть раздел .text. Сегмент 01 - это RW (чтение + запись) без exec, и там есть раздел .data.

Тег LOAD означает, что они отображаются в виртуальном адресном пространстве процесса. Некоторые разделы (например, отладочная информация) не являются и являются просто метаданными для других инструментов. Но NASM помечает неизвестные имена разделов как прогбиты, то есть загруженные, поэтому он мог связываться и иметь нагрузку, а не segfault.


После исправления для использования section .data ваша программа запускается без ошибок segfaulting .

Цикл выполняется в течение одной итерации, поскольку 2 байта, следующие за step: dw 1, не равны нулю. После загрузки меча RAX = 0x2c0001 в моей системе. (cmp между 0x002c0002 и 0xa делает условие LE ложным, потому что оно не меньше или не равно.)

dw означает «слово данных» или «определить слово». Используйте dd для меча данных .


Кстати, нет необходимости хранить счетчик циклов в памяти . Вы ни для чего не используете RDI, RSI, RBP или R8..R15, так что вы можете просто сохранить это в реестре. Как mov edi, limit перед циклом и dec edi / jnz внизу.

Но на самом деле вы должны использовать 64-битный syscall ABI, если вы хотите создать 64-битный код, а не 32-битный int 0x80 ABI. Что произойдет, если вы используете 32-битный int 0x80 Linux ABI в 64-битном коде? . Или создайте 32-битные исполняемые файлы, если вы следуете руководству или учебнику, написанному для этого.

В любом случае, в этом случае вы сможете использовать ebx в качестве счетчика циклов, потому что системный вызов ABI использует разные аргументы для регистров.

...