Надо сказать, что в предыдущей заметке про inline-ассемблер было написано не всё, а также допущена масса грубых ошибок, которые были выявлены и исправлены в процессе компиляции исходников при помощи GNU C++.
Об этом я расскажу чуть позже. Сейчас же, после очень большой проделанной работы (кстати, ещё не до конца) по созданию универсального способа разделять один и тот же ассемблерный код между разными компиляторами, позволю себе похоливарить.
Я не доверяю коду, который сгенерировал компилятор, до тех пор, пока не посмотрю его листинг. В Open Watcom листинг объектного файла можно получить при помощи утилиты wdis:
wdis <object_file> -l=<listing_file>
При этом, будет сгенерирован, например, такой вполне читабельный и понятный листинг:
Module: /home/sadko/eclipse/xsystem/xskernel/include/arch/i386/ipc/initial.cpp GROUP: 'DGROUP' CONST,CONST2,_DATA,_BSS Segment: _TEXT PARA USE32 00000044 bytes 0000 8D 40 00 lea eax,[eax] 0003 8B C9 mov ecx,ecx 0005 49 6E 69 74 69 61 6C 69 7A 65 0A Initialize. Routine Size: 16 bytes, Routine Base: _TEXT + 0000 0010 void near initial_thread_t::Initialize( task_tag_t near * ): 0010 53 push ebx 0011 51 push ecx 0012 89 C3 mov ebx,eax 0014 B8 80 00 00 00 mov eax,0x00000080 0019 E8 00 00 00 00 call void near * near TTSS::operator new( int unsigned ) 001E 89 C2 mov edx,eax 0020 89 D8 mov eax,ebx 0022 E8 00 00 00 00 call int unsigned near common_thread_t::AllocSelector( TTSS near * ) 0027 50 push eax 0028 68 00 00 00 00 push offset L$2 002D 89 C3 mov ebx,eax 002F E8 00 00 00 00 call int near k_printf( char const near *, ... ) 0034 83 C4 08 add esp,0x00000008 0037 89 D8 mov eax,ebx 0039 0F 00 D8 ltr ax 003C E9 00 00 00 00 jmp L$1 0041 L$1: 0041 59 pop ecx 0042 5B pop ebx 0043 C3 ret Routine Size: 52 bytes, Routine Base: _TEXT + 0010 No disassembly errors Segment: CONST BYTE USE32 00000019 bytes 0000 L$2: 0000 5B 74 73 73 5D 3A 20 4C 6F 61 64 69 6E 67 20 54 [tss]: Loading T 0010 52 3A 20 25 30 38 78 0A 00 R: %08x.. Segment: CONST2 PARA USE32 00000000 bytes Segment: _DATA BYTE USE32 00000000 bytes Segment: _BSS BYTE USE32 00000000 bytes BSS Size: 0 bytes
Здесь я знаю, какая функция что вызывает, и какие данные что значат.
Теперь, ярым любителям GNU GCC представляю аж целых два (!) листинга. Первый был получен при помощи команды:
g++ <source_file> -O3 -S -march=i386 -mtune=pentiumpro -nodefaultlibs <object_file>
И получаем следующий плохо читаемый код:
.file "initial.cpp"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "[tss]: Loading TR: %08x\n"
.text
.align 2
.p2align 4,,15
.globl _ZN16initial_thread_t10InitializeEP10task_tag_t
.type _ZN16initial_thread_t10InitializeEP10task_tag_t, @function
_ZN16initial_thread_t10InitializeEP10task_tag_t:
.LFB332:
pushl %ebp
.LCFI0:
movl %esp, %ebp
.LCFI1:
pushl %ebx
.LCFI2:
subl $20, %esp
.LCFI3:
movl $128, (%esp)
call _ZN4TTSSnwEj
movl %eax, 4(%esp)
movl 8(%ebp), %eax
movl %eax, (%esp)
call _ZN15common_thread_t13AllocSelectorEP4TTSS
movl $.LC0, (%esp)
movl %eax, %ebx
movl %eax, 4(%esp)
call _Z8k_printfPKcz
movl %ebx, %eax
#APP
.intel_syntax noprefix
ltr ax
jmp 1f
1:
.att_syntax noprefix
#NO_APP
addl $20, %esp
popl %ebx
popl %ebp
ret
.LFE332:
.size _ZN16initial_thread_t10InitializeEP10task_tag_t, .-_ZN16initial_thread_t10InitializeEP10task_tag_t
.globl __gxx_personality_v0
.section .eh_frame,"a",@progbits
.Lframe1:
.long .LECIE1-.LSCIE1
.LSCIE1:
.long 0x0
.byte 0x1
.string "zP"
.uleb128 0x1
.sleb128 -4
.byte 0x8
.uleb128 0x5
.byte 0x0
.long __gxx_personality_v0
.byte 0xc
.uleb128 0x4
.uleb128 0x4
.byte 0x88
.uleb128 0x1
.align 4
.LECIE1:
.LSFDE1:
.long .LEFDE1-.LASFDE1
.LASFDE1:
.long .LASFDE1-.Lframe1
.long .LFB332
.long .LFE332-.LFB332
.uleb128 0x0
.byte 0x4
.long .LCFI0-.LFB332
.byte 0xe
.uleb128 0x8
.byte 0x85
.uleb128 0x2
.byte 0x4
.long .LCFI1-.LCFI0
.byte 0xd
.uleb128 0x5
.byte 0x4
.long .LCFI3-.LCFI1
.byte 0x83
.uleb128 0x3
.align 4
.LEFDE1:
.ident "GCC: (GNU) 4.1.2 20061115 (prerelease) (SUSE Linux)"
.section .note.GNU-stack,"",@progbits
Нет, конечно, assemblable output не претендует быть суперчитабельным. Аналогично поступает и утилита wdis, если ей задать ключ ‘-a’:
.387
.386p
.model flat
PUBLIC `W?Initialize$:initial_thread_t$n(pn$task_tag_t$$)v`
EXTRN `W?$nw:TTSS$n(ui)pnv`:BYTE
EXTRN `W?AllocSelector$:common_thread_t$n(pn$TTSS$$)ui`:BYTE
EXTRN `W?k_printf$n(pnxae)i`:BYTE
DGROUP GROUP CONST,CONST2,_DATA,_BSS
_TEXT SEGMENT PARA PUBLIC USE32 'CODE'
ASSUME CS:_TEXT, DS:DGROUP, SS:DGROUP
lea eax,[eax]
mov ecx,ecx
DB 49H, 6eH, 69H, 74H, 69H, 61H, 6cH, 69H
DB 7aH, 65H, 0aH
`W?Initialize$:initial_thread_t$n(pn$task_tag_t$$)v`:
push ebx
push ecx
mov ebx,eax
mov eax,80H
call near ptr FLAT:`W?$nw:TTSS$n(ui)pnv`
mov edx,eax
mov eax,ebx
call near ptr FLAT:`W?AllocSelector$:common_thread_t$n(pn$TTSS$$)ui`
push eax
push offset FLAT:L$2
mov ebx,eax
call near ptr FLAT:`W?k_printf$n(pnxae)i`
add esp,8
mov eax,ebx
ltr ax
jmp near ptr L$1
L$1:
pop ecx
pop ebx
ret
_TEXT ENDS
CONST SEGMENT BYTE PUBLIC USE32 'DATA'
L$2:
DB 5bH, 74H, 73H, 73H, 5dH, 3aH, 20H, 4cH
DB 6fH, 61H, 64H, 69H, 6eH, 67H, 20H, 54H
DB 52H, 3aH, 20H, 25H, 30H, 38H, 78H, 0aH
DB 0
CONST ENDS
CONST2 SEGMENT PARA PUBLIC USE32 'DATA'
CONST2 ENDS
_DATA SEGMENT BYTE PUBLIC USE32 'DATA'
_DATA ENDS
_BSS SEGMENT BYTE PUBLIC USE32 'BSS'
_BSS ENDS
END
Единственное, чего я не понимаю — что G++ там в конце понагенерил. Ну да ладно, спишем пока на мои незнания.
Ок, но всё же хочется получить листинг, поэтому я прибегаю к утилите objdump:
objdump -D -Mintel <object_file> > <listing_file>
И получаю ещё менее читабельную и понятную фигню:
.bin/xskernel/include/arch/i386/ipc/initial.cpp.o: file format elf32-i386
Disassembly of section .text:
00000000 <_ZN16initial_thread_t10InitializeEP10task_tag_t>:
0: 55 push ebp
1: 89 e5 mov ebp,esp
3: 53 push ebx
4: 83 ec 14 sub esp,0x14
7: c7 04 24 80 00 00 00 mov DWORD PTR [esp],0x80
e: e8 fc ff ff ff call f <_ZN16initial_thread_t10InitializeEP10task_tag_t+0xf>
13: 89 44 24 04 mov DWORD PTR [esp+4],eax
17: 8b 45 08 mov eax,DWORD PTR [ebp+8]
1a: 89 04 24 mov DWORD PTR [esp],eax
1d: e8 fc ff ff ff call 1e <_ZN16initial_thread_t10InitializeEP10task_tag_t+0x1e>
22: c7 04 24 00 00 00 00 mov DWORD PTR [esp],0x0
29: 89 c3 mov ebx,eax
2b: 89 44 24 04 mov DWORD PTR [esp+4],eax
2f: e8 fc ff ff ff call 30 <_ZN16initial_thread_t10InitializeEP10task_tag_t+0x30>
34: 89 d8 mov eax,ebx
36: 0f 00 d8 ltr ax
39: eb 00 jmp 3b <_ZN16initial_thread_t10InitializeEP10task_tag_t+0x3b>
3b: 83 c4 14 add esp,0x14
3e: 5b pop ebx
3f: 5d pop ebp
40: c3 ret
Disassembly of section .rodata.str1.1:
00000000 <.rodata.str1.1>:
0: 5b pop ebx
1: 74 73 je 76 <_ZN16initial_thread_t10InitializeEP10task_tag_t+0x76>
3: 73 5d jae 62 <_ZN16initial_thread_t10InitializeEP10task_tag_t+0x62>
5: 3a 20 cmp ah,BYTE PTR [eax]
7: 4c dec esp
8: 6f outs [dx],DWORD PTR ds:[esi]
9: 61 popa
a: 64 69 6e 67 20 54 52 imul ebp,DWORD PTR fs:[esi+103],0x3a525420
11: 3a
12: 20 25 30 38 78 0a and BYTE PTR ds:0xa783830,ah
...
Disassembly of section .eh_frame:
00000000 <.eh_frame>:
0: 18 00 sbb BYTE PTR [eax],al
2: 00 00 add BYTE PTR [eax],al
4: 00 00 add BYTE PTR [eax],al
6: 00 00 add BYTE PTR [eax],al
8: 01 7a 50 add DWORD PTR [edx+80],edi
b: 00 01 add BYTE PTR [ecx],al
d: 7c 08 jl 17 <.eh_frame+0x17>
f: 05 00 00 00 00 add eax,0x0
14: 00 0c 04 add BYTE PTR [esp+eax],cl
17: 04 88 add al,0x88
19: 01 00 add DWORD PTR [eax],eax
1b: 00 18 add BYTE PTR [eax],bl
1d: 00 00 add BYTE PTR [eax],al
1f: 00 20 add BYTE PTR [eax],ah
21: 00 00 add BYTE PTR [eax],al
23: 00 00 add BYTE PTR [eax],al
25: 00 00 add BYTE PTR [eax],al
27: 00 41 00 add BYTE PTR [ecx],al
2a: 00 00 add BYTE PTR [eax],al
2c: 00 41 0e add BYTE PTR [ecx+14],al
2f: 08 85 02 42 0d 05 or BYTE PTR [ebp+0x50d4202],al
35: 44 inc esp
36: 83 .byte 0x83
37: 03 .byte 0x3
Disassembly of section .comment:
00000000 <.comment>:
0: 00 47 43 add BYTE PTR [edi+67],al
3: 43 inc ebx
4: 3a 20 cmp ah,BYTE PTR [eax]
6: 28 47 4e sub BYTE PTR [edi+78],al
9: 55 push ebp
a: 29 20 sub DWORD PTR [eax],esp
c: 34 2e xor al,0x2e
e: 31 2e xor DWORD PTR [esi],ebp
10: 32 20 xor ah,BYTE PTR [eax]
12: 32 30 xor dh,BYTE PTR [eax]
14: 30 36 xor BYTE PTR [esi],dh
16: 31 31 xor DWORD PTR [ecx],esi
18: 31 35 20 28 70 72 xor DWORD PTR ds:0x72702820,esi
1e: 65 gs
1f: 72 65 jb 86 <_ZN16initial_thread_t10InitializeEP10task_tag_t+0x86>
21: 6c ins BYTE PTR es:[edi],[dx]
22: 65 gs
23: 61 popa
24: 73 65 jae 8b <_ZN16initial_thread_t10InitializeEP10task_tag_t+0x8b>
26: 29 20 sub DWORD PTR [eax],esp
28: 28 53 55 sub BYTE PTR [ebx+85],dl
2b: 53 push ebx
2c: 45 inc ebp
2d: 20 4c 69 6e and BYTE PTR [ecx+ebp*2+110],cl
31: 75 78 jne ab <_ZN16initial_thread_t10InitializeEP10task_tag_t+0xab>
33: 29 00 sub DWORD PTR [eax],eax
Такой распорядок вещей мне не нравится.
Вопрос знатокам: есть возможность получить нормальный (или, хотя бы, более понятный) ассемблерный листинг, чтобы увидеть хотя бы, какие функции вызываются функцией, и какие данные где расположены?
А так, имхо, Watcom пока побеждает по читабельности дизассемблерного кода.
Блин, Владимир… ну это не серьезно…
В принципе мыслю, что компилятору нафиг не нужно генерировать юзерфрендли формат. Это промежуточное представление при компиляции и только.
Что касается имен C++, то это называется Mangled name… чтобы увидеть их в человеческом виде — используй demangle.
который в objdump вызывается через -C.
А зачем jmp после ltr?
На всякий пожарный, для i486, у которого может не сброситься очередь предвыборки. Чтобы сбросить очередь предвыборки, делается jump. Кстати, если мне не изменяет память, сама intel рекомендовала в своих мануалах.
Re: Блин, Владимир… ну это не серьезно…
О, спасибо! Заюзал опцию, названия функций стали более читабельными.
Остаётся вопрос за:
- вызовом функции k_printf, который ну никак не показывается в листинге;
- изображением секции данных как секции данных, а не как кода.
Если мне не изменяет память, то я сам тебе когда-то это рекомендовал
. Только, я думаю, что при загрузке tr сбрасывать очередь не обязательно.
Re: Блин, Владимир… ну это не серьезно…
Насчет printk пока не знаю, а для отмены дизассемблирования секций данных нужна опция -d вместо опции -D. -D==disassemble-all, т.е. все, что только можно дизассемблировать
. А -d — только секции, где предполагается наличие кода.
Дрон, это ты, что ли, анонимайзишься?
Возможно. Только это делается один раз на один процессор, дальше — тупо переключение через jump, поэтому, в принципе, можно и оставить.
Re: Блин, Владимир… ну это не серьезно…
Насчёт опции -d я знаю, вот mangled names проглядел.
>Дрон, это ты, что ли, анонимайзишься?
Мимо
.
>Возможно. Только это делается один раз на один процессор, дальше — тупо
>переключение через jump, поэтому, в принципе, можно и оставить.
Ну, хуже от нее, конечно, не будет, просто непонятно было, зачем.
Re: Блин, Владимир… ну это не серьезно…
А с какой оптимизацией компилировал? Без оптимизации gcc вставляет странную феерию со стеком (в прологе что-то вроде:
mov eax, 0xF
add eax, 0xF
shr eax, 0×4
shl eax, 0×4
sub esp, eax
но могу и ошибаться, на память написал). Для чего это надо, остается догадываться.
Нет, это не я…
Насчет стека… у gcc есть опция для типов — align че-то типа того…
Если такую переменную расположить в стеке — gcc по идее должен выровнять ее смещение. Не могу точно сказать какое выравнивание по умолчанию… Во всяком случае вышеуказанные манипуляции очень похожи на выравнивание.
А я почти параллельно с ltr делаю переход на адрес первой задачи, поэтому у меня так и так получается переход.
Мммм. Кто ж такой читает мой ЖЖ, но не признаётся
SII, ты, что ль?
Re: Блин, Владимир… ну это не серьезно…
Компилил с -O3 (мега-злая оптимизация
). Кстати, припаял ещё опции -mregparm=3 -mtune=pentiumpro, код стал покрасивше (да-да, я очень люблю передачу параметров в регистрах).