LINUX.ORG.RU

Хеллоуворлд на С из ассемблерных вставок

 , , ,


0

0

Здравствуйте. Я тут попробовал сделать сабж, столкнулся с некоторым не совсем понятным явлением. Вот этот код под 64 бит

const char msg[] = {'H','e','l','l','o',',',' ','W','o','r','l','d','\n'};
const char sz = sizeof(msg);

void _start(void)
{
  asm volatile (
  "mov $1,  %%rax\n\t" //  1 - SYS_write
  "mov $1,  %%rdi\n\t" //  1 - STDOUT_FILENO
  "mov %0,  %%rsi\n\t" // msg
  "mov %1,  %%rdx\n\t" // size
  "syscall\n"
  :
  : "g" (msg), "g" (sz)
  : "%rsi", "%rdx"
  );
  
  
  asm volatile (
  "mov $60, %rax\n\t" // 60 - SYS_exit
  "mov $0,  %rdi\n\t" //  0 - EXIT_SUCCESS
  "syscall\n");
  __builtin_unreachable();
}
компилируется таким образом: gcc -O3 -nostartfiles -nostdlib -o hello hello.c и работает нормально.

Если же вынести объявление-инициализацию переменных в _start :


void _start(void)
{
  const char msg[] = {'H','e','l','l','o',',',' ','W','o','r','l','d','\n'};
  const char sz = sizeof(msg);
  asm volatile (
  "mov $1,  %%rax\n\t" //  1 - SYS_write
  "mov $1,  %%rdi\n\t" //  1 - STDOUT_FILENO
  "mov %0,  %%rsi\n\t" // msg
  "mov %1,  %%rdx\n\t" // size
  "syscall\n"
  :
  : "g" (msg), "g" (sz)
  : "%rsi", "%rdx"
  );
  
  
  asm volatile (
  "mov $60, %rax\n\t" // 60 - SYS_exit
  "mov $0,  %rdi\n\t" //  0 - EXIT_SUCCESS
  "syscall\n");
  __builtin_unreachable();
}
никакого текста не выводится.

gcc version 4.5.1 20101208

Почему так?

★★★★★

Потому что глобальные переменная находится в сегменте данных, а локальная с стеке и адреса явно должны быть разные.
А по тексту
«mov %0, %%rsi\n\t» // msg
«mov %1, %%rdx\n\t» // size
одинаковая адресация.

Скомпиляй gcc -nostartfiles -nostdlib -o hello hello.c -S и разницу посмотри. Вообще, если делаешь вставку на асме, должен понимать и асм и С, а не гадать на кофейной гуще.

ilovewindows ★★★★★
()
Ответ на: комментарий от pacify

Вот для первого случая:

	.file	"hello.c"
	.text
	.p2align 4,,15
.globl _start
	.type	_start, @function
_start:
.LFB0:
	.cfi_startproc
#APP
# 5 "hello.c" 1
	mov $1, %rax
	mov $1,  %rdi
	mov $msg, %rsi
	mov $13, %rdx
	syscall

# 0 "" 2
# 17 "hello.c" 1
	mov $60, %rax
	mov $0, %rdi
	syscall

# 0 "" 2
#NO_APP
	.cfi_endproc
.LFE0:
	.size	_start, .-_start
.globl msg
	.data
	.type	msg, @object
	.size	msg, 13
msg:
	.byte	72
	.byte	101
	.byte	108
	.byte	108
	.byte	111
	.byte	44
	.byte	32
	.byte	87
	.byte	111
	.byte	114
	.byte	108
	.byte	100
	.byte	10
.globl sz
	.section	.rodata
	.type	sz, @object
	.size	sz, 1
sz:
	.byte	13
	.ident	"GCC: (SUSE Linux) 4.5.1 20101208 [gcc-4_5-branch revision 167585]"
	.section	.comment.SUSE.OPTs,"MS",@progbits,1
	.string	"Ospwg"
	.section	.note.GNU-stack,"",@progbits

Вот для второго (который не работает):
	.file	"hello.c"
	.text
	.p2align 4,,15
.globl _start
	.type	_start, @function
_start:
.LFB0:
	.cfi_startproc
	leaq	-24(%rsp), %rax
#APP
# 5 "hello.c" 1
	mov $1, %rax
	mov $1,  %rdi
	mov %rax, %rsi
	mov $13, %rdx
	syscall

# 0 "" 2
# 17 "hello.c" 1
	mov $60, %rax
	mov $0, %rdi
	syscall

# 0 "" 2
#NO_APP
	.cfi_endproc
.LFE0:
	.size	_start, .-_start
	.ident	"GCC: (SUSE Linux) 4.5.1 20101208 [gcc-4_5-branch revision 167585]"
	.section	.comment.SUSE.OPTs,"MS",@progbits,1
	.string	"Ospwg"
	.section	.note.GNU-stack,"",@progbits

Если второй вариант запустить под valgrind, получается такое:
...
==6573== Syscall param write(buf) points to unaddressable byte(s)
==6573==    at 0x40016F: ??? (in /home/user/test/hello)
==6573==  Address 0x1 is not stack'd, malloc'd or (recently) free'd
...

SZT ★★★★★
() автор топика

Вот еще что характерно. Если скомпилировать такое со стандартными библиотеками (gcc -O3 -nostartfiles -o hello hello.c)

#include <unistd.h>

void _start(void)
{
  char msg[] = {'H','e','l','l','o',',',' ','W','o','r','l','d','\n'};
  const char sz = sizeof(msg);
  write (STDOUT_FILENO, msg, sz);
  asm volatile (
  "mov $1, %%rax\n\t"  //  1 - SYS_write
  "mov $1,  %%rdi\n\t" //  1 - STDOUT_FILENO
  "mov %0, %%rsi\n\t"  // msg
  "mov %1, %%rdx\n\t"  // size
  "syscall\n"
  :
  : "g" (msg), "g" (sz)
  : "%rsi", "%rdx"
  );
  
  
  asm volatile (
  "mov $60, %rax\n\t" // 60 - SYS_exit
  "mov $0, %rdi\n\t"  //  0 - EXIT_SUCCESS
  "syscall\n");
  __builtin_unreachable();
}
Оно два раза выводит Hello, World

И в этом случае оно буковки в стек запишет:

...
	movq	%rsp, %rsi
	movb	$72, (%rsp)
	movb	$101, 1(%rsp)
	movb	$108, 2(%rsp)
	movb	$108, 3(%rsp)
	movb	$111, 4(%rsp)
	movb	$44, 5(%rsp)
	movb	$32, 6(%rsp)
...
	call	write
#APP
# 8 "hello.c" 1
	mov $1, %rax
	mov $1,  %rdi
	mov %rsp, %rsi
	mov $13, %rdx
	syscall
...

SZT ★★★★★
() автор топика

const char msg[] = {'H','e','l','l','o',',',' ','W','o','r','l','d','\n'};

однако, опасно играешь. NULL в конце не забыл?

demidrol ★★★★★
()
Ответ на: комментарий от AptGet

потому что ты в clobber list добавил не все используемые регистры

Зачем? Системный вызов write принимает три аргумента

ssize_t write(int fd, const void *buf, size_t count);
В ассемблерной вставке все три аргумента заданы:
  "mov $1, %%rdi\n\t" //  1 - STDOUT_FILENO (fd)
  "mov %0, %%rsi\n\t"  // msg (*buf)
  "mov %1, %%rdx\n\t"  // size (count)
Второй и третий есть в clobber list. Первый не вижу смысла туда добавлять.

SZT ★★★★★
() автор топика
Ответ на: комментарий от SZT

потому что ты их используешь в ассемблерной вставке. и memory добавить не забудь

" We shoudn’t list the input and output registers in this list. Because, gcc knows that «asm» uses them (because they are specified explicitly as constraints). If the instructions use any other registers, implicitly or explicitly (and the registers are not present either in input or in the output constraint list), then those registers have to be specified in the clobbered list. "

AptGet ★★★
()
Ответ на: комментарий от SZT

Ну вот ошибка-то:

mov %rax, %rsi

В %rax 1 перед вызовом. А еще перед этим туда пишут стековый адрес msg. Синтаксис at&t не знаю, поэтому как эту ситуацию разруливать тоже не знаю. Может там надо где-то указать, что он задействуется при вызове? Или, по старинке, кадр стека как-то можно сделать.

ien
()
Ответ на: комментарий от AptGet

Спасибо, помогло. Вот так работает:

void _start(void)
{
  char msg[] = {'H','e','l','l','o',',',' ','W','o','r','l','d','\n'};
  const char sz = sizeof(msg);
  asm volatile (
  "mov $1, %%rax\n\t"  //  1 - SYS_write
  "mov $1, %%rdi\n\t"  //  1 - STDOUT_FILENO
  "mov %0, %%rsi\n\t"  // msg
  "mov %1, %%rdx\n\t"  // size
  "syscall\n"
  :
  : "g" (msg), "g" (sz)
  : "%rsi", "%rdx", "rax", "rdi", "memory"
  );
  
  
  asm volatile (
  "mov $60,%%rax\n\t" // 60 - SYS_exit
  "mov $0, %%rdi\n\t" //  0 - EXIT_SUCCESS
  "syscall\n"
  :
  :
  : "%rax", "%rdi");
  __builtin_unreachable();
}
Кстати, в C можно как-нибудь делать не нуль-терминированные строки без использования такой {'H','e','l','l','o',',',' ','W','o','r','l','d','\n'} неуклюжей конструкции? И почему это gcc делает
...
	movq	%rsp, %rsi
	movb	$72, (%rsp)
	movb	$101, 1(%rsp)
	movb	$108, 2(%rsp)
	movb	$108, 3(%rsp)
...
Вместо более очевидного использования инструкции push и последующего восстановления значения регистра %rsp ? Да и через push будет по нескольку байтов сразу записываться, что быстрее.

SZT ★★★★★
() автор топика
Ответ на: комментарий от SZT

Кстати, в C можно как-нибудь делать не нуль-терминированные строки без использования такой {'H','e','l','l','o',',',' ','W','o','r','l','d','\n'} неуклюжей конструкции?

можно, вот так например:

char str[7] = "hello!\n";

это можно в макрос завернуть

И почему это gcc делает
[...]
Вместо более очевидного использования инструкции push и последующего восстановления значения регистра %rsp ? Да и через push будет по нескольку байтов сразу записываться, что быстрее.

этого я не знаю, может специалисты по всему по оптимизации заглянут в тред и объяснят

AptGet ★★★
()

Поразително!

Замена куска

const char msg[] = {'H','e','l','l','o',',',' ','W','o','r','l','d','\n'};
на
const char msg[13] = "Hello, World\n";
внутри _start() привела к сильным изменениям. Вместо кучи инструкций movb было сгенерировано всего три, записывающие эту фразу
	movabsq	$6278066737626506568, %rax  # "Hello, W"
	movl	$1684828783, -16(%rsp)      # "orld"
	movb	$10, -12(%rsp)              # "\n"
	movq	%rax, -24(%rsp)
	leaq	-24(%rsp), %rcx

SZT ★★★★★
() автор топика
Ответ на: Поразително! от SZT

Почему GCC для const char msg[] = {'H','e','l','l','o',',',' ','W','o','r','l','d','\n'}; делает много movb, а для const char msg[13] = «Hello, World\n»; всего три? Это недоработка в компиляторе или так надо? Если надо, зачем?

SZT ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.