Ассемблер (в Linux)

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Ассемблер (в Linux)

Непрочитанное сообщение Olej » 09 фев 2023, 20:30

Ассемблер сам по себе меня мало интересует...
Только:
а). применительно к ассемблерным врезкам в коде (модулей) ядра Linux (модули ядра (римэйк) + Книга: "Расширения ядра Linux: драйверы и модули")
б). и только в виде инлайновых врезок GCC в C код.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Ассемблер (в Linux)

Непрочитанное сообщение Olej » 09 фев 2023, 20:50

Olej писал(а):
09 фев 2023, 20:30
Только:
Но, может кого-то заинтересует ассемблер глубже ... а ещё поэтому - я буду выкладывать здесь ссылки на источники на ассемблерные описания.

Ассемблер вообще:
Reverse Engineering для начинающих

Шпаргалка по основным инструкциям ассемблера x86/x64

Полная документация GNU binutils: Documentation for binutils 2.40 ... В составе ассемблер as (AT&T): Using as.
This file is a user guide to the GNU assembler as (GNU Binutils) version 2.40.

Код: Выделить всё

olej@R420:~$ as --version
GNU ассемблер (GNU Binutils for Ubuntu) 2.38
Copyright (C) 2022 Free Software Foundation, Inc.
Эта программа является открытым программным обеспечением; вы можете
распространять её согласно условиям GNU General Public License версии 3 или
более новой версии.
Эта программа не имеет абсолютно никаких гарантий.
Ассемблер настроен на цель x86_64-linux-gnu.
Ассемблер inline в GCC:
6.47 How to Use Inline Assembly Language in C Code
6.47.1 Basic Asm — Assembler Instructions Without Operands
6.47.2 Extended Asm - Assembler Instructions with C Expression Operands

How to Convert Basic asm to Extended asm

Ассемблерные вставки в компиляторе gcc
МГУ ©2013
Ассемблерные вставки в GCC

GCC Inline ASM

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Ассемблер (в Linux)

Непрочитанное сообщение Olej » 09 фев 2023, 21:08

Olej писал(а):
09 фев 2023, 20:30
только в виде инлайновых врезок GCC в C код
По синтаксису записи ... от самого элементарного - сам синтаксис записи:

Код: Выделить всё

int main(int argc, char *argv[]) {
   asm volatile(
      "nop\n"
      "nop\n"
      "nop\n"
   );
   return 0;
}
Это уже компилируется (и исполняется) нормально.

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$ make nop
cc -Wall -Wno-unused-result -Wno-format -O3 -O3 nop.c -o nop

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$ ls -l nop*
-rwxrwxr-x 1 olej olej 15776 фев  9 20:01 nop
-rw-r--r-- 1 olej olej   122 фев  7 18:19 nop.c

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$ ldd nop
	linux-vdso.so.1 (0x00007ffcb45d8000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fead7c4c000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fead7ea0000)

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$ ./nop 
olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$
P.S. Все файлы, даже тривиальные, как этот, прикладываю (сохраняю) - как подтверждение того что именно это и происходит.
Вложения
nop.c
(122 байт) 19 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Ассемблер (в Linux)

Непрочитанное сообщение Olej » 09 фев 2023, 21:41

Olej писал(а):
09 фев 2023, 21:08
сам синтаксис записи:
Olej писал(а):
09 фев 2023, 20:50
Ассемблерные вставки в компиляторе gcc
Важно помнить, что после компиляции Си-программы, код ассемблерной вставки
будет транслироваться gas, а не nasm. Естественным синтаксисом gas является AT&T, существенно
отличающийся от синтаксиса Intel. Современные версии gas поддерживают синтаксис Intel,
переключиться на него можно специальной директивой, но диалект поддерживаемого синтаксиса
будет отличаться от диалекта nasm рядом особенностей.
Синтаксис оператора ассемблерной вставки следующий.
__asm__ (вставка : список_выходных_операндов : список_входных_операндов :
список_разрушаемых_регистров );

Код: Выделить всё

__asm__ ( ".intel_syntax noprefix\n\t"
	"mov eax, %1\n\t"
	"mov %0, eax\n\t" /* ассемблерная вставка */
	: "=r"(b) /* выходные операнды */
	: "r"(a) /* входные операнды */
	: "%eax" /* разрушаемые регистры */
);
вставка представляет собой строковую константу с ассемблерными инструкциями. В теле вставки
могут находиться не только ассемблерные инструкции, но и вообще любые директивы,
распознаваемые ассемблером gas. В частности, это позволяет изменить используемый им
синтаксис инструкций по-умолчанию.
Пример 1

Код: Выделить всё

__asm__ ( ".intel_syntax noprefix\n\t"
	"mov eax, %1\n\t"
	"mov %0, eax\n\t" /* ассемблерная вставка */
	: "=r"(b) /* выходные операнды */
	: "r"(a) /* входные операнды */
	: "%eax" /* разрушаемые регистры */
);
Директива .intel_syntax меняет синтаксис AT&T на синтаксис Intel; необходимо
дополнительно указывать смену синтаксиса для операндов инструкций, noprefix, что позволит
писать код в более близком к диалекту nasm виде, не используя при записи имен регистров
префикс %.
Для связи ассемблерных инструкций с переменными Си-программы используются следующие за
вставкой два элемента оператора: списки операндов, в которых операнды перечислены через
запятую. Каждый описанный операнд затем может использоваться в ассемблерных инструкциях,
обращение к нему осуществляется по номеру с префиксом %. Нумерация начинается с 0, и идет
непрерывно, объединяя все элементы списков выходных и входных операндов.
Для выходных операндов строка ограничения типа должна начинаться с символа `=`. Следующий
в строке символ указывает (кодирует), куда компилятору нужно поместить значение
соответствующей переменной. Способ кодировки одинаковый для входных и выходных
операндов вставки.
: "=r"(b) /* выходные операнды */
: "r"(a) /* входные операнды */
Для рассматриваемого примера описание операндов требует размещения значения Си-
переменной a на каком-либо регистре общего назначения (задано символом r), перед тем как
начнет выполняться код вставки. После того как код вставки завершится, значение переменной b
будет находиться на регистре общего назначения и его потребуется записать в переменную b.
Пример того, как компилятор о
Компилятору о побочном эффекте, возникающем из-за копирования, необходимо сообщить
ключевым словом __volatile__ после ключевого слова __asm__. Это обезопасит корректность
программы, поскольку в отсутствии этого ключевого слова некоторые оптимизирующие
преобразования потенциально могут перемещать код, в том числе и код ассемблерных вставок.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Ассемблер (в Linux)

Непрочитанное сообщение Olej » 09 фев 2023, 22:29

Olej писал(а):
09 фев 2023, 20:50
Ассемблерные вставки в GCC
Вернуться к началу
Выполнив команду

Код: Выделить всё

gcc test_asm.cpp -S
получим файл test_asm.s
...
При желании, можно получить несколько более подробный код (с дополнительными комментариями), воспользовавшись опцией -fverbose-asm.
Построчный листинг (в файле test_asm.lst) C++ и ассемблера можно получить, воспользовавшись командой

Код: Выделить всё

gcc -c -g -Wa,-a,-ad test_asm.cpp > test_asm.lst
GCC вставляет, после возможных макроподстановок, текст ассемблерной вставки непосредственно в выходной файл на языке ассемблера.
...
Во-первых, компилятор почти не "подглядывает" в указанный программистом код ассемблерной вставки. Он при необходимости проводит макроподстановки (см. ниже), но в общем передаёт код ассемблеру почти "как есть", и почти не имеет представления о происходящем внутри. В частности, обнаружением ошибок занимается именно ассемблер (GCC передаёт программисту сообщения об ошибках от ассемблера).

Во-вторых, как следствие, ассемблерная вставка является для компилятора (и в частности оптимизатора) единой непрозрачной командой (чёрным ящиком). Всё нужную ему информацию о том, как этот блок взаимодействует с окружающим миром, компилятор получает напрямую от программиста, из явно указанных операндов ассемблерной вставки (задающих связи ассемблерного кода с переменными C++ и список задействованных ресурсов (регистров и т. д.) и изменений (состояния флагов, памяти и т. д.) в ассемблерной вставке), а не из детального рассмотрения текста ассемблерной вставки (которого он не производит). Технически говоря, GCC предоставляет программисту интерфейс к Register Transfer Language. Ответственность о соответствии действительности информации, указанной в операндах, целиком лежит на программисте.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Ассемблер (в Linux)

Непрочитанное сообщение Olej » 09 фев 2023, 23:40

Olej писал(а):
09 фев 2023, 21:08
уже компилируется (и исполняется) нормально.
Дальше...
То как производятся все системные вызовы Linux в 32-бит системе:

Код: Выделить всё

$ cat write.32.c 
#include <string.h>
#include <unistd.h>
#include <stdio.h>

#include <asm/unistd_32.h>
#ifdef __i386__ 
int write_call(int fd, const char* str, int len) { 
   long __res;           // это ещё C определение
   __asm__ volatile ("int $0x80" 
           : "=a" (__res) 
           : "0"(__NR_write),"b"((long)(fd)),"c"((long)(str)),"d"((long)(len))); 
   return (int) __res;   // а это уже C оператор
}
#endif 

int main(int argc, char *argv[]) {
#ifdef __i386__ 
   char* string = "русская строка для вывода\n";
   if (!write(1, string, strlen(string)))
      printf("ошибка вывода\n");   
   return 0;
#else 
   char* string = "это написано только для 32-бит Linux!\n";
   if(!write(1, string, strlen(string)))
      printf("ошибка вывода\n");
   return 1;   
#endif 
}
Выполнение в 32-бит системе:

Код: Выделить всё

$ inxi -S
System:    Host: lmde32 Kernel: 5.10.0-21-686 i686 bits: 32 Desktop: Cinnamon 5.6.7 Distro: LMDE 5 Elsie

$ uname -a
Linux lmde32 5.10.0-21-686 #1 SMP Debian 5.10.162-1 (2023-01-21) i686 GNU/Linux

Код: Выделить всё

$ make
cc -Wall -Wno-unused-result -O3 -O3 write.32.c -o write.32

Код: Выделить всё

$ ./write.32 
русская строка для вывода
В 64-бит системе системный вызов делается не через int 0x80 / irret (но и в 32-бит тоже может быть так), через команды sysenter / sysexit (или syscall / sysret в x86_64 архитектуре ARM):

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$ make write.32
cc -Wall -Wno-unused-result -Wno-format -O3 -O3 write.32.c -o write.32

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$ ./write.32 
это написано только для 32-бит Linux!
Вложения
write.32.c
(870 байт) 20 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Ассемблер (в Linux)

Непрочитанное сообщение Olej » 10 фев 2023, 02:16

Olej писал(а):
09 фев 2023, 23:40
Дальше...
Чтение счётчика RDTSC числа циклов тактовой частоты процессора ... и оценка частоты работы процессора:

Код: Выделить всё

olej@esprimop420:~/2023/own.BOOKs/GASM.next.inWORK$ cat clock.c
#include <stdint.h>
#include <time.h>
#include <stdio.h>

uint64_t rdtsc(void) {
   union sc {
      struct {uint32_t lo, hi;} r;
      uint64_t f;
   } sc;
   __asm__ __volatile__ ("rdtsc" : "=a"(sc.r.lo), "=d"(sc.r.hi));
   return sc.f;
}
// вариант для старых GCC:
// __asm__ __volatile__ ( ".byte 0x0f, 0x31" : "=a"( sc.r.lo ), "=d"( sc.r.hi ) );

// вариант но только для 32-бит i686:
// asm volatile ( "rdtsc" : "=A" (x) );

#define NUMB 10
uint64_t calibr(int rep) {
   uint64_t n, m, sum = 0;
   n = m = (rep >= 0 ? NUMB : rep);
   while(n--) {
      uint64_t cf, cs;
      cf = rdtsc();
      cs = rdtsc();
      sum += cs - cf;
   }
   return sum / m;
}

uint64_t proc_hz(void) {
   time_t t1, t2;
   int64_t cf = 0, cs = 0;
   time(&t1);
   while(t1 == time(&t2))         // начало след. секунды
      cf = rdtsc();
   while(t2 == time(&t1))         // начало след. секунды
      cs = rdtsc();
   return cs - cf - calibr(1000); // с учётом времени вызова rdtsc() 
}

int main(int argc, char *argv[]) {
   for (int i = 1; i < 5; i++)
      printf("%016lX | ", rdtsc());
   printf("\n");      
   for (int i = 10; i < 5000; i *= 10)
      printf("%06X | ", calibr(i)); 
   printf("\n");
   printf("%llu\n", proc_hz());  
   return 0;
}

Код: Выделить всё

olej@esprimop420:~/2023/own.BOOKs/GASM.next.inWORK$ make clock
cc -Wall -Wno-unused-result -Wno-format -O3 -O3 clock.c -o clock

Код: Выделить всё

olej@esprimop420:~/2023/own.BOOKs/GASM.next.inWORK$ sudo chrt -r 50 taskset -c 0 ./clock
[sudo] пароль для olej: 
000274A63DFA328E | 000274A63DFD95CC | 000274A63DFD9B3B | 000274A63DFD9D04 | 
000016 | 000016 | 000016 | 
3392144142

Код: Выделить всё

olej@esprimop420:~/2023/own.BOOKs/GASM.next.inWORK$ inxi -Cxxx
CPU:       Info: Quad Core model: Intel Xeon E3-1240 v3 bits: 64 type: MT MCP arch: Haswell rev: 3 
           L2 cache: 8 MiB 
           flags: avx avx2 lm nx pae sse sse2 sse3 sse4_1 sse4_2 ssse3 vmx bogomips: 54276 
           Speed: 3592 MHz min/max: 800/3800 MHz Core speeds (MHz): 1: 3592 2: 3592 3: 3592 4: 3592 
           5: 3592 6: 3592 7: 3590 8: 3594 
Вложения
clock.c
(1.3 КБ) 18 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Ассемблер (в Linux)

Непрочитанное сообщение Olej » 10 фев 2023, 18:53

Olej писал(а):
10 фев 2023, 02:16
Дальше...
Длина текстовой строки... Разминаемся... :-D
Эквивалент вот этого:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$ cat strlen0.c
#include <stdio.h>
#include <string.h>

long lenstr(char* s) {
	return strlen(s);
}

int main(int argc, char **argv) {
	char* str = "test string";
	if (argc > 1) str = argv[1];
	printf("строка: <%s> длина %ld байт\n", str, lenstr(str));
}

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$ make strlen0
cc -Wall -Wno-unused-result -Wno-format -O3 -O3 strlen0.c -o strlen0

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$ ./strlen0
строка: <test string> длина 11 байт

olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$ ./strlen0 'русская строка'
строка: <русская строка> длина 27 байт
Теперь то же на инлайн ассемблере:

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$ cat strlen1.c
#include <stdio.h>
#include <stdint.h>

long lenstr(char* s) {
	long res = 0;          // это ещё C определение
	__asm__ volatile (
		"0:\n\t"       //метка
		"incq %0\n\t"
		"cmpb $0,(%1,%0)\n\t"
		"jne 0b\n\t"   //переход назад (b)
	: "=rax"(res), "=D"(s)
	: "0"(res), "D"(s)
	: "cc"
	);
	return res;
}

int main(int argc, char **argv) {
	char* str = "test string";
	if (argc > 1) str = argv[1];
	printf("строка: <%s> длина %ld байт\n", str, lenstr(str));
}

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$ make strlen1
cc -Wall -Wno-unused-result -Wno-format -O3 -O3 strlen1.c -o strlen1

Код: Выделить всё

olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$ ./strlen1
строка: <test string> длина 11 байт

olej@R420:~/2023/own.BOOKs/BHV.kernel.new/GASM.next.inWORK$ ./strlen1 'русская строка'
строка: <русская строка> длина 27 байт
Вложения
strlen0.c
(254 байт) 20 скачиваний
strlen1.c
(509 байт) 19 скачиваний

Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 6 гостей