LINUX.ORG.RU

Где я ошибся в примитивном коде?

 , странный баг


0

2

Есть g++ 4.7.2 и вот такой примитивный код:

#define linux_syscall3( result, command, first, second, third ) __asm__ __volatile__ ("int $0x80" : "=a" ( result ) : "a" ( command ), "b" ( first ), "c" ( second ), "d" ( third ) );

int write( const char *text, const unsigned int len )
{
    int result;
    linux_syscall3( result, 4, 1 ,text, len );
    return result;
}

int read( char *buffer, const unsigned int len )
{
    int result;
    linux_syscall3( result, 3, 0 ,buffer, len );
    return result;
}

class Test
{
public:
    Test( const char *text, unsigned int len )
    {
        write( text, len );
    }
    ~Test()
    {
    }
};

int main()
{
    static const char text[] = "Constructor!\r\n";
    static char buffer[512];
    int count = read( buffer, 512 );
    Test test( text, sizeof( text ) );
    write( buffer, count );
    return 0;
}
По идеи этот код должен делать вот что:
1) Прочитать строку из консоли.
2) Создать класс, и в конструкторе записать в консоль строку «Constructor!\r\n».
3) Записать в консоль то, что было прочитано в п.1

Но вот запись в консоль в конструкторе каким-то образом не дает выполниться п.3. Если же объявление класса перенести ниже вывода введенной строки то все работает. write возвращает ошибку -14( как я понял это значит что неправильный адрес?). При чем это только в релизе, в дебаге все работает нормально.
Собственно где я ошибся?

★★

примитивный код

__asm__ __volatile__ («int $0x80» : «=a» ( result ) : «a» ( command ), «b» ( first ), «c» ( second ), «d» ( third ) );
linux_syscall3( result, 3, 0 ,buffer, len );
linux_syscall3( result, 4, 1 ,text, len );

Ага.

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

Это всего-лишь системные вызовы read и write в консоль.

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

отличный пример того как _не надо_ делать.

exception13 ★★★★★
()

Использование асма без особо на то весских причин уже как минимум одна особо тяжкая ошибка.

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

Это — «низкоуровневая» (в кавычках, потому что C++) работа с процессором, с линуксом это никак не связано (кроме разве что int 0x80).

beastie ★★★★★
()

Мне кажется, надо сохранять значения регистров и восстанавливать после прерывания, параметры вроде через них передаются, а предыдущая информация затирается, вот фигня какая то и получается.

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

Да, действительно добавил вот это:

#define SAVE_ALL   \
    "cld;"         \
    "pushq %rax;"  \
    "pushq %rbx;"  \
    "pushq %rcx;"  \
    "pushq %rdx;"  \
    "pushq %rsi;"  \
    "pushq %rdi;"  \
    "pushq %rbp;"  \
    "pushq %r8;"   \
    "pushq %r9;"   \
    "pushq %r10;"  \
    "pushq %r11;"  \
    "pushq %r12;"  \
    "pushq %r13;"  \
    "pushq %r14;"  \
    "pushq %r15;"

#define RESTORE_ALL \
    "popq  %r15;"   \
    "popq  %r14;"   \
    "popq  %r13;"   \
    "popq  %r12;"   \
    "popq  %r11;"   \
    "popq  %r10;"   \
    "popq  %r9;"    \
    "popq  %r8;"    \
    "popq  %rbp;"   \
    "popq  %rdi;"   \
    "popq  %rsi;"   \
    "popq  %rdx;"   \
    "popq  %rcx;"   \
    "popq  %rbx;"   \
    "popq  %rax;"

int write( volatile const char *text, const unsigned int len )
{
    int result;
    __asm__ __volatile__ ( SAVE_ALL );
    linux_syscall3( result, 4, 1 ,text, len );
    __asm__ __volatile__ ( RESTORE_ALL );
    return result;
}

int read( char *buffer, const unsigned int len )
{
    int result;
    __asm__ __volatile__ ( SAVE_ALL );
    linux_syscall3( result, 3, 0 ,buffer, len );
    __asm__ __volatile__ ( RESTORE_ALL );
    return result;
}
И стало работать нормально.
Но вот если я выключаю стандартную либу "-nostdlib" и добавляю этот код:
asm (
".globl _start;"
"_start:;"
"call  main;"
"mov  $1,   %eax;"
"xor  %ebx, %ebx;"
"int   $0x80;"
);
То снова начинает глючить. Я и гуглил на эту тему, но толком не могу найти как же мне это все добро использовать правильно.

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

а ты посмотри как в libc сделано

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

Да нечего там проверять, они отрабатывают правильно.
Причину я нашел, компилятор как-то по странному оптимизирует. Он по идеи должен был в переменную result положить содержимое rax, а затем я уже этот rax вместе с остальными восстанавливаю. После этого компилятор сам решает как передавать переменную result. Но он блин почему-то решил хранить ее в rax и когда я восстанавливаю регистры, естественно ее затираю. Я решил поставить переменной result модификатор volatile. Он стал как и положенно ее куда-то копировать:

  400220:       53                      push   %rbx
  400221:       fc                      cld    
  400222:       50                      push   %rax
  400223:       53                      push   %rbx
  400224:       51                      push   %rcx
  400225:       52                      push   %rdx
  400226:       56                      push   %rsi
  400227:       57                      push   %rdi
  400228:       55                      push   %rbp
  400229:       41 50                   push   %r8
  40022b:       41 51                   push   %r9
  40022d:       41 52                   push   %r10
  40022f:       41 53                   push   %r11
  400231:       41 54                   push   %r12
  400233:       41 55                   push   %r13
  400235:       41 56                   push   %r14
  400237:       41 57                   push   %r15
  400239:       31 db                   xor    %ebx,%ebx
  40023b:       b8 03 00 00 00          mov    $0x3,%eax
  400240:       48 89 f9                mov    %rdi,%rcx
  400243:       89 f2                   mov    %esi,%edx
  400245:       cd 80                   int    $0x80
  400247:       89 44 24 fc             mov    %eax,-0x4(%rsp)
  40024b:       41 5f                   pop    %r15
  40024d:       41 5e                   pop    %r14
  40024f:       41 5d                   pop    %r13
  400251:       41 5c                   pop    %r12
  400253:       41 5b                   pop    %r11
  400255:       41 5a                   pop    %r10
  400257:       41 59                   pop    %r9
  400259:       41 58                   pop    %r8
  40025b:       5d                      pop    %rbp
  40025c:       5f                      pop    %rdi
  40025d:       5e                      pop    %rsi
  40025e:       5a                      pop    %rdx
  40025f:       59                      pop    %rcx
  400260:       5b                      pop    %rbx
  400261:       58                      pop    %rax
  400262:       8b 44 24 fc             mov    -0x4(%rsp),%eax
  400266:       5b                      pop    %rbx
  400267:       c3                      retq
Но это блин не помогает, все равно значение теряется. Пробовал юзать gdb или ddd, но не увидел как у них можно по инструкции выполнять и смотреть что творится в регистрах и на стеке, неужели в линуксе нет подобия ollydbg? Я то думал тут будет куча удобных средств для дебага, а найти что-то нормальное не могу.
Я не пойму почему вот это «mov %eax,-0x4(%rsp)» и «mov -0x4(%rsp),%eax» приводит к потере переменной? Я конечно могу не восстанавливать rax, работать будет но вдруг компилятор снова что-то наоптимизирует.
А вот при подключение стандартной библиотеки в rax оказывался не ноль а большое положительное значение, которое и создавало иллюзию того, что оно все правильно считывало.
Как правильно разрулить такую ситуацию?

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

По поводу просмотра регистров: http://stackoverflow.com/questions/5429137/how-to-print-register-values-in-gdb

Вообще, ты можешь выполнять свой код в каком-нить эмуляторе (qemu, вроде, подойдёт) и тогда вообще всё что угодно будет доступно.

По поводу rax: в него возвращается код возврата. Ты бы всё же почитал какой-нить мануал...

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

Это то я знаю, и вроде как не опасно не сохранять и восстанавливать rax( к тому-же в документации GCC сказано что компилятор якобы знает какие я регистры меняю если использую регистры через одно буквенные имена как в дефайне syscall3 ).
Про то что при volatile пропадает значение, я наверно понял, оно сохраняется в стек, но я же в стек сохраняю регистры. А так как компилятор про это не в курсе то и получается что восстанавливается не то что надо.
Я бы просто не сохранял и восстанавливал бы rax еслиб во всех исходниках что я нагуглил не было видно что они сохраняют и восстанавливают rax.
Я опасаюсь что компилятор может что-то держать в rax а я его не сохраню и значение потеряется. Хорошо было бы еслиб rax сохранять, затем после вызова класть rax в переменную на стеке, и затем восстанавливать rax. Тогда компилятор бы 100% всегда правильно бы все оптимизировал и небыло бы всяких глюков. Но вот как объяснить компилятору что я в данный момент кладу на стек столько-то байтов и затем читаю столько-же. Чтоб он при volatile правильно смещение в стеке выбирал.

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

Естественно, и не один раз. Вот там выше как-раз то про что я и говорил:

In all the three examples above, we didn’t put any register to the clobber list. why? In the first two examples, GCC decides the registers and it knows what changes happen. In the last one, we don’t have to put ecx on the c lobberlist, gcc knows it goes into x. Therefore, since it can know the value of ecx, it isn’t considered clobbered. 
Тоесть по идеи компилятор в курсе что меняется регистр. Вот как бы ему еще сказать что меняется стек, про это я не нашел.
Или ты советуешь не париться и просто не сохранять/восстанавливать rax? Ибо по идеи компилятор в курсе изменения онного и должен правильно разрулить эту ситуацию?

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

Вот как бы ему еще сказать что меняется стек

может просто возвращать стек в исходное состояние? Не спец в этом всём, но, мне кажется, это лучшая стратегия.

Или ты советуешь не париться и просто не сохранять/восстанавливать rax?

если бы я что-то в этом понимал... Я думаю пофиг, вот сдесь вот сказано какие регистры точно не надо трогать (не знаю на сколько это актуально): http://www.delorie.com/djgpp/doc/ug/asm/calling.html

Тем более что EAX это такая вещь которая везде используется. На всякий случай я бы указал rax как clobbered и посмотрел влияет ли это на генерируемый код или нет.

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

Так оно записывает как раз после записи в стек, но перед чтением из стека. Короче это вариант я решил не использовать.
Дело в том что в clobbered он запрещает указывать те регистры которы я использую в input, так как они автоматически помещаются в clobbered.
Я пришел к выводу что можно не заниматься восстановлением rax, и компилятор все сам разрулит. А если и не разрулит, то можно будет обратиться к разработчикам компилятора.
Всем спасибо за помощь, я считаю что проблема решена.

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

man syscall

anonymous
()
Ответ на: комментарий от V1KT0P

Пробовал юзать gdb или ddd, но не увидел как у них можно по инструкции выполнять и смотреть что творится в регистрах и на стеке

в gdb: nexti, stepi — для продвижения по инструкциям. Смотреть регистры через display $eax, undisplay $eax или info registers, info all-registers. Просто распечатать значение регистра: print $ebx.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Ну смотри, мне надо поставить бряк перед самым выполнением «_start:». Как это сделать? Поробовал DDD как-то удалось приостановить выполнение, затем нажал nexti и показ регистров, так оно вообще зависло. Пришлось насильно его закрывать.
Неужели сложно было за столько лет сделать хотя бы примитивный аналог ollydbg? Мне все-го то надо видеть дизасм проги, текущее положение в нем, стек, регистры, установку бряка на любую строку дизасма и две кнопки: продолжить выполнение до первого бряка или выполнить одну инструкцию..

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

Ну смотри, мне надо поставить бряк перед самым выполнением «_start:». Как это сделать?

b _start

Неужели сложно было за столько лет сделать хотя бы примитивный аналог ollydbg?

Я как-то тоже таким вопросом задавался, пока не понял, что в мире, где используется gdb и ddd всегда есть исходники и отладочная информация.

Мне все-го то надо видеть дизасм проги, текущее положение в нем, стек, регистры, установку бряка на любую строку дизасма и две кнопки: продолжить выполнение до первого бряка или выполнить одну инструкцию..

Все программы появляются от «мне надо, а такого нет».

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Тю я дурак, у меня-же в релизной версии отладочная информация вырезалась после компиляции и «break _start» не отрабатывал. Отключил вырезалку дебаг информации и действительно заработало. Спасибо, буду дебажить.
Исходники то да, но вот у меня ситуация все исходники есть, а хорошего дебаггера который сразу бы показал мне где я ошибся нету. И вместо этого мне пришлось создавать этот топик.

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

дебаггера который сразу бы показал мне где я ошибся, нету

Reading symbols from /tmp/a.out...(no debugging symbols found)...done.

Написано же.

i-rinat ★★★★★
()
Ответ на: комментарий от false

Огромное спасибо, это именно то что я хотел. Да оно еще и на Qt написано, вообще сказка. Почему я про эту прогу первый раз слышу? Реально годная вещь.

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

В Ubuntu тоже, но прекрасно собирается из исходников.

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

у gdb есть tui попробуй его

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

Для начала я решил попробовать int 0x80. Но я уже попробовал и syscall. Правда для него дефайны для 5 и 6 параметров получились уродливые.

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