Вот мне всегда было интересно как имплементится на машинном уровне поведение такого кода:class Foo {
static void Main() {
bool jump = true; Label:
try {
System.Console.WriteLine("try");
if (jump) {
jump = false;
goto Label;
}
}
finally {
System.Console.WriteLine("finally");
}
}
}Который выводит на экран:try
finally
try
finallyТо есть любой выход из блока try, даже прыжком “вверх” по коду, должен завершаться вызовом блока finally. Я с ассемблером не дружу, но оказалось всё очень просто: 4: static void Main() {
...
5: bool jump = true;
00000039 mov eax,1
0000003e and eax,0FFh
00000043 mov dword ptr [ebp-24h],eax
00000046 nop
6:
7: Label:
8: try {
00000047 nop
9: System.Console.WriteLine("try");
00000048 mov ecx,dword ptr ds:[02C32030h]
0000004e call 641F703C
00000053 nop // в зависимости от условия
10: if (jump) { // к выходу из try-блока
00000054 cmp dword ptr [ebp-24h],0
00000058 sete al
0000005b movzx eax,al
0000005e mov dword ptr [ebp-28h],eax
00000061 cmp dword ptr [ebp-28h],0
00000065 jne 00000083
00000067 nop
11: jump = false;
00000068 xor edx,edx
0000006a mov dword ptr [ebp-24h],edx
12: goto Label;
0000006d nop
0000006e mov dword ptr [ebp-1Ch],0 // сохраняем
00000075 mov dword ptr [ebp-18h],0FCh // в стек
0000007c push 3C012Dh // <== адрес перехода @ 00000047
00000081 jmp 0000009A
13: }
14: }
00000083 nop
00000084 nop
00000085 mov dword ptr [ebp-1Ch],0
0000008c mov dword ptr [ebp-18h],0FCh // или
00000093 push 3C0136h // <== адрес перехода @ 000000ac
00000098 jmp 0000009A
15: finally {
0000009a nop
16: System.Console.WriteLine("finally");
0000009b mov ecx,dword ptr ds:[02C32034h]
000000a1 call 641F703C
000000a6 nop
17: }
000000a7 nop // после finally каждый раз
000000a8 pop eax // достаём адрес из стека
000000a9 jmp eax // и прыгаем по нему
000000ab nop
18:
19: Debugger.Break();
000000ac call 64700020
000000b1 nop
20: }
...
То есть найдя прыжок из try-блока, JIT-компилятор сгенерировал после finally-блока прыжок по адресу из вершины стека и убедился, что при любом из выходов из try в стек положат адрес кода, с которого следует продолжать исполнение. Аналогичная магия происходит при использовании break, continue и return внутри try { }. Read more: Control::Flow
static void Main() {
bool jump = true; Label:
try {
System.Console.WriteLine("try");
if (jump) {
jump = false;
goto Label;
}
}
finally {
System.Console.WriteLine("finally");
}
}
}Который выводит на экран:try
finally
try
finallyТо есть любой выход из блока try, даже прыжком “вверх” по коду, должен завершаться вызовом блока finally. Я с ассемблером не дружу, но оказалось всё очень просто: 4: static void Main() {
...
5: bool jump = true;
00000039 mov eax,1
0000003e and eax,0FFh
00000043 mov dword ptr [ebp-24h],eax
00000046 nop
6:
7: Label:
8: try {
00000047 nop
9: System.Console.WriteLine("try");
00000048 mov ecx,dword ptr ds:[02C32030h]
0000004e call 641F703C
00000053 nop // в зависимости от условия
10: if (jump) { // к выходу из try-блока
00000054 cmp dword ptr [ebp-24h],0
00000058 sete al
0000005b movzx eax,al
0000005e mov dword ptr [ebp-28h],eax
00000061 cmp dword ptr [ebp-28h],0
00000065 jne 00000083
00000067 nop
11: jump = false;
00000068 xor edx,edx
0000006a mov dword ptr [ebp-24h],edx
12: goto Label;
0000006d nop
0000006e mov dword ptr [ebp-1Ch],0 // сохраняем
00000075 mov dword ptr [ebp-18h],0FCh // в стек
0000007c push 3C012Dh // <== адрес перехода @ 00000047
00000081 jmp 0000009A
13: }
14: }
00000083 nop
00000084 nop
00000085 mov dword ptr [ebp-1Ch],0
0000008c mov dword ptr [ebp-18h],0FCh // или
00000093 push 3C0136h // <== адрес перехода @ 000000ac
00000098 jmp 0000009A
15: finally {
0000009a nop
16: System.Console.WriteLine("finally");
0000009b mov ecx,dword ptr ds:[02C32034h]
000000a1 call 641F703C
000000a6 nop
17: }
000000a7 nop // после finally каждый раз
000000a8 pop eax // достаём адрес из стека
000000a9 jmp eax // и прыгаем по нему
000000ab nop
18:
19: Debugger.Break();
000000ac call 64700020
000000b1 nop
20: }
...
То есть найдя прыжок из try-блока, JIT-компилятор сгенерировал после finally-блока прыжок по адресу из вершины стека и убедился, что при любом из выходов из try в стек положат адрес кода, с которого следует продолжать исполнение. Аналогичная магия происходит при использовании break, continue и return внутри try { }. Read more: Control::Flow
0 comments:
Post a Comment