При работе с данных следует учитывать порядок байтов. В настоящее время распростраенны два порядка байтов: big-endian и little-endian.
Порядок big-endian предполагает расположение байтов от старшего к младшему. Порядок little-endian предполагает расположение байтов
от младшего к старшему. Например, возьмем шестнадцатеричное число 0x01234567
. Это число состоит из четырех байтов. В архитектурах с big-endian байты этого числа будут расположены
следующим образом:
Адрес | 0x0000 | 0x0001 | 0x0002 | 0x0003 |
Значение | 01 | 23 | 45 | 67 |
То есть сначала идет самый старший байт.
В архитектурах с little-endian байты этого числа будут расположены в обратном порядке:
Адрес | 0x0000 | 0x0001 | 0x0002 | 0x0003 |
Значение | 67 | 45 | 23 | 01 |
То есть сначала идет самый младший байт.
Архитектура Intel x86-64 для расположения байтов применяет порядок little-endian. Почему это важно? Возьмем следующую программу под Linux:
global _start section .data number dd 0x01234567 ; 4 байт section .text _start: movzx rdi, byte [number] ; получаем 1-й байт mov rax, 60 syscall
В данном случае в секции данных определено 4-байтное число 0x01234567. Старший байт числа равен "0x01", а самый младший байт числа - "0x67" (103 в десятичной системе). В программе мы получаем первый байт в регистр rdi. Запустим программу:
root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o root@Eugene:~/asm# ld -o hello hello.o root@Eugene:~/asm# ./hello root@Eugene:~/asm# echo $? 103 root@Eugene:~/asm#
Мы видим, что самый первый байт равен 103 или 0x67. То есть самый первый байт - это самый младший байт. Соответственно, чтобы получить самый старший байт (четвертый байт), нам надо прибавить смещение в 3 байта:
movzx rdi, byte [number+2]
Но возьмем другую программу (также на Linux):
global _start section .data numbers db 0x01, 0x23, 0x45, 0x67 ; 4 байт section .text _start: movzx rdi, byte [numbers] ; получаем 1-й байт mov rax, 60 syscall
Здесь также в секции данных определены 4 байта, но эти байты представляют массив. Данные массива в памяти расположены в порядке его определения, то есть сначала идет первый байт массива, потом второй и т.д. В итоге в регистр RDI будет помещено число 0х01:
root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o root@Eugene:~/asm# ld -o hello hello.o root@Eugene:~/asm# ./hello root@Eugene:~/asm# echo $? 1 root@Eugene:~/asm#
Возьмем более сложный пример:
global _start section .data numbers dw 0x0123, 0x4567 ; 4 байта - каждое число по 2 байта section .text _start: movzx rdi, byte [numbers] ; получаем 1-й байт mov rax, 60 syscall
Опять у нас в секции данных 4 байта, но теперь это массив из двубайтовых чисел (слов). Первое число равно 0x0123
. Но в соответствии с little-endian опять же сначала будет
располагаться младший байт числа, а потом старший:
Адрес | 0x0000 | 0x0001 |
Значение | 23 | 01 |
Соответственно в регистр RDI будет помещен младший байт числа - 0x23 (число 35 в десятичной системе):
root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o root@Eugene:~/asm# ld -o hello hello.o root@Eugene:~/asm# ./hello root@Eugene:~/asm# echo $? 35 root@Eugene:~/asm#
Возьмем четвертую ситуацию:
global _start section .data number db "01234567" ; 8 байт section .text _start: movzx rdi, byte [number] ; получаем 1-й байт mov rax, 60 syscall
Теперь число представлено строкой ASCII. Каждый символ в строке ASCII занимает 1 байт, соответственно строка number занимает 8 байт. Но в плане размещения данных строка символов фактически эквивалентна массиву - в данном случае массиву байт. То есть сначала идет байт (числовой код), который представляет символ "0", далее идет байт символа "1" и так далее. Поэтому в регистре RDI окажется символ 48 - числовой код символа "0":
root@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o root@Eugene:~/asm# ld -o hello hello.o root@Eugene:~/asm# ./hello root@Eugene:~/asm# echo $? 48 root@Eugene:~/asm#