How can I set a breakpoint in an async function? - rust

I have a struct with an async method which I'd like to debug. I used gdb to set a breakpoint with a debug build. Here is how the code looks like when stopping at the async method Strct::async_method:
0x5555557f4a6a <bin::Strct::async_method+26> mov QWORD PTR [rsp+0x10],rsi
0x5555557f4a6f <bin::Strct::async_method+31> mov QWORD PTR [rsp+0x18],rdx
0x5555557f4a74 <bin::Strct::async_method+36> mov BYTE PTR [rsp+0x112],0x0
0x5555557f4a7c <bin::Strct::async_method+44> lea rsi,[rsp+0x10]
0x5555557f4a81 <bin::Strct::async_method+49> mov QWORD PTR [rsp+0x8],rax
0x5555557f4a86 <bin::Strct::async_method+54> call 0x5555557e6970 <core::future::from_generator>
The code calls core::future::from_generator which is not what I'd like to debug. What is the proper way to suspend the execution of the async method body?

Let's use this as our MRE
use futures::executor; // 0.3.5
pub fn exercise() {
executor::block_on(example());
}
#[inline(never)]
async fn example() {
canary()
}
#[inline(never)]
fn canary() {}
If you view the assembly for this, you'll see how the compiler implements async functions. The async function returns a type implementing impl Future, which is powered under the hood by a generator:
playground::example:
subq $24, %rsp
movb $0, 16(%rsp)
movzbl 16(%rsp), %edi
callq *core::future::from_generator#GOTPCREL(%rip)
movb %al, 23(%rsp)
movb 23(%rsp), %al
movb %al, 8(%rsp)
movb 8(%rsp), %al
addq $24, %rsp
retq
The actual body of the async function is moved into the generator, which happens to use the name {{closure}}:
playground::example::{{closure}}:
;; Lots of instructions removed
movq playground::canary#GOTPCREL(%rip), %rcx
callq *%rcx
jmp .LBB20_2
;; Even more removed
Thus, you can set a breakpoint on that generated function:
(lldb) br set -r '.*example.*closure.*'
(lldb) r
Process 28101 launched: '/tmp/f/target/debug/f' (x86_64)
Process 28101 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100000ab0 f`f::example::_$u7b$$u7b$closure$u7d$$u7d$::h2b30ad777e7395f3((null)=(pointer = 0x00007ffeefbff328), (null)=ResumeTy # 0x00007ffeefbff070) at main.rs:8:20
5 }
6
7 #[inline(never)]
-> 8 async fn example() {
9 canary()
10 }
11
Target 0: (f) stopped.
You could also set a breakpoint on the desired line:
(lldb) breakpoint set --file /private/tmp/f/src/main.rs --line 9
(lldb) r
Process 28113 launched: '/tmp/f/target/debug/f' (x86_64)
Process 28113 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100000ad8 f`f::example::_$u7b$$u7b$closure$u7d$$u7d$::h2b30ad777e7395f3((null)=(pointer = 0x00007ffeefbff328), (null)=ResumeTy # 0x00007ffeefbff070) at main.rs:9:5
6
7 #[inline(never)]
8 async fn example() {
-> 9 canary()
10 }
11
12 #[inline(never)]
Target 0: (f) stopped.
See also:
What is the concrete type of a future returned from `async fn`?
What is the purpose of async/await in Rust?
Unable to set a breakpoint on main while debugging a program compiled with Rust 1.10 with GDB
How to use a debugger like GDB or LLDB to debug a crate in Rust?
Can I set an LLDB breakpoint when multiple Rust source files share the same name?

Related

linux syscall using spinlock returning value to userspace

I'm, currently struggling with the correct implementation of a kernel-spinlock in combination with a return statement which should return a value to userspace. I implemented a kernel syscall 'sys_kernel_entropy_is_recording' which should return the value of a kernel-variable 'is_kernel_entropy_recording':
asmlinkage bool sys_kernel_entropy_is_recording(void)
{
spin_lock(&entropy_analysis_lock);
return is_kernel_entropy_recording;
spin_unlock(&entropy_analysis_lock);
}
At this point arise two questions:
Q1: Is this implementation correct at all, meaning will the correct value of 'is_kernel_entropy_recording' be returned to userspace and afterwards the spinlock be released?
My concerns are:
a) is it allowed to return a value from kernelspace to userspace this way at all?
b) the return statement is located before the spin_unlock statement, hence will spin_unlock be even called?
Q2: To answer these question myself I disassembled the compiled .o file but determined (at least it looks for me like) the spin_lock/spin_unlock calls are completely ignored by the compiler, as it just moves the value of 'sys_kernel_entropy_is_recording' to eax an calls ret (I'm not sure about line 'callq 0xa5'):
(gdb) disassemble /m sys_kernel_entropy_is_recording
Dump of assembler code for function sys_kernel_entropy_is_recording:
49 {
0x00000000000000a0 <+0>: callq 0xa5 <sys_kernel_entropy_is_recording+5>
0x00000000000000a5 <+5>: push %rbp
0x00000000000000ad <+13>: mov %rsp,%rbp
50 spin_lock(&entropy_analysis_lock);
51 return is_kernel_entropy_recording;
52 spin_unlock(&entropy_analysis_lock);
53 }
0x00000000000000b5 <+21>: movzbl 0x0(%rip),%eax # 0xbc <sys_kernel_entropy_is_recording+28>
0x00000000000000bc <+28>: pop %rbp
0x00000000000000bd <+29>: retq
Hence I guess the application of spinlock is not correct.. Could someone please give me an advice for an appropriate approach?
Thanks a lot in advance!
It is prohibited to return from syscall with spinlock holded. And, as usual with C code, none instruction is executed after return statement.
Common practice is to save value obtained under lock into local variable, and return value of this variable after unlock:
bool ret;
spin_lock(&entropy_analysis_lock);
ret = is_kernel_entropy_recording;
spin_unlock(&entropy_analysis_lock);
return ret;

VC++ SSE code generation - is this a compiler bug?

A very particular code sequence in VC++ generated the following instruction (for Win32):
unpcklpd xmm0,xmmword ptr [ebp-40h]
2 questions arise:
(1) As far as I understand the intel manual, unpcklpd accepts as 2nd argument a 128-aligned memory address. If the address is relative to a stack frame alignment cannot be forced. Is this really a compiler bug?
(2) Exceptions are thrown from at the execution of this instruction only when run from the debugger, and even then not always. Even attaching to the process and executing this code does not throw. How can this be??
The particular exception thrown is access violation at 0xFFFFFFFF, but AFAIK that's just a code for misalignment.
[Edit:]
Here's some source that demonstrates the bad code generation - but typically doesn't cause a crash. (that's mostly what I'm wondering about)
[Edit 2:]
The code sample now reproduces the actual crash. This one also crashes outside the debugger - I suspect the difference occurs because the debugger launches the program at different typical base addresses.
// mock.cpp
#include <stdio.h>
struct mockVect2d
{
double x, y;
mockVect2d() {}
mockVect2d(double a, double b) : x(a), y(b) {}
mockVect2d operator + (const mockVect2d& u) {
return mockVect2d(x + u.x, y + u.y);
}
};
struct MockPoly
{
MockPoly() {}
mockVect2d* m_Vrts;
double m_Area;
int m_Convex;
bool m_ParClear;
void ClearPar() { m_Area = -1.; m_Convex = 0; m_ParClear = true; }
MockPoly(int len) { m_Vrts = new mockVect2d[len]; }
mockVect2d& Vrt(int i) {
if (!m_ParClear) ClearPar();
return m_Vrts[i];
}
const mockVect2d& GetCenter() { return m_Vrts[0]; }
};
struct MockItem
{
MockItem() : Contour(1) {}
MockPoly Contour;
};
struct Mock
{
Mock() {}
MockItem m_item;
virtual int GetCount() { return 2; }
virtual mockVect2d GetCenter() { return mockVect2d(1.0, 2.0); }
virtual MockItem GetItem(int i) { return m_item; }
};
void testInner(int a)
{
int c = 8;
printf("%d", c);
Mock* pMock = new Mock;
int Flag = true;
int nlr = pMock->GetCount();
if (nlr == 0)
return;
int flr = 1;
if (flr == nlr)
return;
if (Flag)
{
if (flr < nlr && flr>0) {
int c = 8;
printf("%d", c);
MockPoly pol(2);
mockVect2d ctr = pMock->GetItem(0).Contour.GetCenter();
// The mess happens here:
// ; 74 : pol.Vrt(1) = ctr + mockVect2d(0., 1.0);
//
// call ? Vrt#MockPoly##QAEAAUmockVect2d##H#Z; MockPoly::Vrt
// movdqa xmm0, XMMWORD PTR $T4[ebp]
// unpcklpd xmm0, QWORD PTR tv190[ebp] **** crash!
// movdqu XMMWORD PTR[eax], xmm0
pol.Vrt(0) = ctr + mockVect2d(1.0, 0.);
pol.Vrt(1) = ctr + mockVect2d(0., 1.0);
}
}
}
void main()
{
testInner(2);
return;
}
If you prefer, download a ready vcxproj with all the switches set from here. This includes the complete ASM too.
Update: this is now a confirmed VC++ compiler bug, hopefully to be resolved in VS2015 RTM.
Edit: The connect report, like many others, is now garbage. However the compiler bug seems to be resolved in VS2017 - not in 2015 update 3.
Since no one else has stepped up, I'm going to take a shot.
1) If the address is relative to a stack frame alignment cannot be forced. Is this really a compiler bug?
I'm not sure it is true that you cannot force alignment for stack variables. Consider this code:
struct foo
{
char a;
int b;
unsigned long long c;
};
int wmain(int argc, wchar_t* argv[])
{
foo moo;
moo.a = 1;
moo.b = 2;
moo.c = 3;
}
Looking at the startup code for main, we see:
00E31AB0 push ebp
00E31AB1 mov ebp,esp
00E31AB3 sub esp,0DCh
00E31AB9 push ebx
00E31ABA push esi
00E31ABB push edi
00E31ABC lea edi,[ebp-0DCh]
00E31AC2 mov ecx,37h
00E31AC7 mov eax,0CCCCCCCCh
00E31ACC rep stos dword ptr es:[edi]
00E31ACE mov eax,dword ptr [___security_cookie (0E440CCh)]
00E31AD3 xor eax,ebp
00E31AD5 mov dword ptr [ebp-4],eax
Adding __declspec(align(16)) to moo gives
01291AB0 push ebx
01291AB1 mov ebx,esp
01291AB3 sub esp,8
01291AB6 and esp,0FFFFFFF0h <------------------------
01291AB9 add esp,4
01291ABC push ebp
01291ABD mov ebp,dword ptr [ebx+4]
01291AC0 mov dword ptr [esp+4],ebp
01291AC4 mov ebp,esp
01291AC6 sub esp,0E8h
01291ACC push esi
01291ACD push edi
01291ACE lea edi,[ebp-0E8h]
01291AD4 mov ecx,3Ah
01291AD9 mov eax,0CCCCCCCCh
01291ADE rep stos dword ptr es:[edi]
01291AE0 mov eax,dword ptr [___security_cookie (12A40CCh)]
01291AE5 xor eax,ebp
01291AE7 mov dword ptr [ebp-4],eax
Apparently the compiler (VS2010 compiled debug for Win32), recognizing that we will need specific alignments for the code, takes steps to ensure it can provide that.
2) Exceptions are thrown from at the execution of this instruction only when run from the debugger, and even then not always. Even attaching to the process and executing this code does not throw. How can this be??
So, a couple of thoughts:
"and even then not always" - Not standing over your shoulder when you run this, I can't say for certain. However it seems plausible that just by random chance, stacks could get created with the alignment you need. By default, x86 uses 4byte stack alignment. If you need 16 byte alignment, you've got a 1 in 4 shot.
As for the rest (from https://msdn.microsoft.com/en-us/library/aa290049%28v=vs.71%29.aspx#ia64alignment_topic4):
On the x86 architecture, the operating system does not make the alignment fault visible to the application. ...you will also suffer performance degradation on the alignment fault, but it will be significantly less severe than on the Itanium, because the hardware will make the multiple accesses of memory to retrieve the unaligned data.
TLDR: Using __declspec(align(16)) should give you the alignment you want, even for stack variables. For unaligned accesses, the OS will catch the exception and handle it for you (at a cost of performance).
Edit1: Responding to the first 2 comments below:
Based on MS's docs, you are correct about the alignment of stack parameters, but they propose a solution as well:
You cannot specify alignment for function parameters. When data that
has an alignment attribute is passed by value on the stack, its
alignment is controlled by the calling convention. If data alignment
is important in the called function, copy the parameter into correctly
aligned memory before use.
Neither your sample on Microsoft connect nor the code about produce the same code for me (I'm only on vs2010), so I can't test this. But given this code from your sample:
struct mockVect2d
{
double x, y;
mockVect2d(double a, double b) : x(a), y(b) {}
It would seem that aligning either mockVect2d or the 2 doubles might help.

Ownership and conditionally executed code

I read the rust book over the weekend and I have a question about the concept of ownership. The impression I got is that ownership is used to statically determine where a resource can be deallocated. Now, suppose that we have the following:
{ // 1
let x; // 2
{ // 3
let y = Box::new(1); // 4
x = if flip_coin() {y} else {Box::new(2)} // 5
} // 6
} // 7
I was surprised to see that the compiler accepts this program. By inserting println!s and implementing the Drop trait for the boxed value, I saw that the box containing the value 1 will be deallocated at either line 6 or 7 depending on the return value of flip_coin. How does the compiler know when to deallocate that box? Is this decided at run-time using some run-time information (like a flag to indicate if the box is still in use)?
After some research I found out that Rust currently adds a flag to every type that implements the Drop trait so that it knows whether the value has been dropped or not, which of course incurs a run-time cost. There have been proposals to avoid that cost by using static drops or eager drops but those solutions had problems with their semantics, namely that drops could occur at places that you wouldn't expect (e.g. in the middle of a code block), especially if you are used to C++ style RAII. There is now consensus that the best compromise is a different solution where the flags are removed from the types. Instead flags will be added to the stack, but only when the compiler cannot figure out when to do the drop statically (while having the same semantics as C++) which specifically happens when there are conditional moves like the example given in this question. For all other cases there will be no run-time cost. It appears though, that this proposal will not be implemented in time for 1.0.
Note that C++ has similar run-time costs associated with unique_ptr. When the new Drop is implemented, Rust will be strictly better than C++ in that respect.
I hope this is a correct summary of the situation. Credit goes to u/dyoll1013, u/pcwalton, u/!!kibwen, u/Kimundi on reddit, and Chris Morgan here on SO.
In non-optimized code, Rust uses dynamic checks, but it's likely that they will be eliminated in optimized code.
I looked at the behavior of the following code:
#[derive(Debug)]
struct A {
s: String
}
impl Drop for A {
fn drop(&mut self) {
println!("Dropping {:?}", &self);
}
}
fn flip_coin() -> bool { false }
#[allow(unused_variables)]
pub fn test() {
let x;
{
let y1 = A { s: "y1".to_string() };
let y2 = A { s: "y2".to_string() };
x = if flip_coin() { y1 } else { y2 };
println!("leaving inner scope");
}
println!("leaving middle scope");
}
Consistent with your comment on the other answer, the call to drop for the String that was left alone occurs after the "leaving inner scope" println. That does seem consistent with one's expectation that the y's scopes extend to the end of their block.
Looking at the assembly language, compiled without optimization, it seems that the if statement not only copies either y1 or y2 to x, but also zeroes out whichever variable provided the source for the move. Here's the test:
.LBB14_8:
movb -437(%rbp), %al
andb $1, %al
movb %al, -177(%rbp)
testb $1, -177(%rbp)
jne .LBB14_11
jmp .LBB14_12
Here's the 'then' branch, which moves the "y1" String to x. Note especially the call to memset, which is zeroing out y1 after the move:
.LBB14_11:
xorl %esi, %esi
movl $32, %eax
movl %eax, %edx
leaq -64(%rbp), %rcx
movq -64(%rbp), %rdi
movq %rdi, -176(%rbp)
movq -56(%rbp), %rdi
movq %rdi, -168(%rbp)
movq -48(%rbp), %rdi
movq %rdi, -160(%rbp)
movq -40(%rbp), %rdi
movq %rdi, -152(%rbp)
movq %rcx, %rdi
callq memset#PLT
jmp .LBB14_13
(It looks horrible until you realize that all those movq instructions are just copying 32 bytes from %rbp-64, which is y1, to %rbp-176, which is x, or at least some temporary that'll eventually be x.) Note that it copies 32 bytes, not the 24 you'd expect for a Vec (one pointer plus two usizes). This is because Rust adds a hidden "drop flag" to the structure, indicating whether the value is live or not, following the three visible fields.
And here's the 'else' branch, doing exactly the same for y2:
.LBB14_12:
xorl %esi, %esi
movl $32, %eax
movl %eax, %edx
leaq -128(%rbp), %rcx
movq -128(%rbp), %rdi
movq %rdi, -176(%rbp)
movq -120(%rbp), %rdi
movq %rdi, -168(%rbp)
movq -112(%rbp), %rdi
movq %rdi, -160(%rbp)
movq -104(%rbp), %rdi
movq %rdi, -152(%rbp)
movq %rcx, %rdi
callq memset#PLT
.LBB14_13:
This is followed by the code for the "leaving inner scope" println, which is painful to behold, so I won't include it here.
We then call a "glue_drop" routine on both y1 and y2. This seems to be a compiler-generated function that takes an A, checks its String's Vec's drop flag, and if that's set, invokes A's drop routine, followed by the drop routine for the String it contains.
If I'm reading this right, it's pretty clever: even though it's the A that has the drop method we need to call first, Rust knows that it can use ... inhale ... the drop flag of the Vec inside the String inside the A as the flag that indicates whether the A needs to be dropped.
Now, when compiled with optimization, inlining and flow analysis should recognize situations where the drop definitely will happen (and omit the run-time check), or definitely will not happen (and omit the drop altogether). And I believe I have heard of optimizations that duplicate the code following a then/else clause into both paths, and then specialize them. This would eliminate all run-time checks from this code (but duplicate the println! call).
As the original poster points out, there's an RFC proposal to move drop flags out of the values and instead associate them with the stack slots holding the values.
So it's plausible that the optimized code might not have any run-time checks at all. I can't bring myself to read the optimized code, though. Why not give it a try yourself?

Can i use rust instead of c++ in OS Development

I want to know if rust complied code have OS dependent code in it or not.(not talking about print like stuff)
for example
let x = (4i,2i,3i)
let y = (3i,4i,4i)
now if compare x == y is it using some of its library and if yes is platform dependent.
Edited:
Like in C++ we should not use new, try catch, or any standard lib.
what are the things we should be avoid while writing in rust.
You can see the code that the rust compiler will generate for a snippet like that yourself, without having to even install Rust locally.
Just visit the web-based playpen, and type your snippet in there. You can run the program (and thus observe what it does via print statements), or, more usefully in this case, you can compile the program down to the generated assembly and then inspect it to see if it has calls to underlying system routines.
If you go to this link: http://is.gd/Be6YVJ I have already put such a program into the playpen. (See bottom of this post for the actual program text.)
If you hit the asm button, you can then see the assembly for each routine. (I have added inline(never) attributes to the relevant functions to ensure that they do not get optimized away by the compiler.)
Here is the generated assembly for bar below, a function that calls out to a higher-order function to get a pair of 3-tuples, and then compares them for equality:
.section .text._ZN3bar20h2bb2fd5b9c9e987beaaE,"ax",#progbits
.align 16, 0x90
.type _ZN3bar20h2bb2fd5b9c9e987beaaE,#function
_ZN3bar20h2bb2fd5b9c9e987beaaE:
.cfi_startproc
cmpq %fs:112, %rsp
ja .LBB0_2
movabsq $56, %r10
movabsq $0, %r11
callq __morestack
retq
.LBB0_2:
subq $56, %rsp
.Ltmp0:
.cfi_def_cfa_offset 64
movq %rdi, %rax
leaq 8(%rsp), %rdi
callq *%rax
movq 8(%rsp), %rcx
xorl %eax, %eax
cmpq 32(%rsp), %rcx
jne .LBB0_5
movq 40(%rsp), %rcx
cmpq %rcx, 16(%rsp)
jne .LBB0_5
movq 48(%rsp), %rax
cmpq %rax, 24(%rsp)
sete %al
.LBB0_5:
addq $56, %rsp
retq
.Ltmp1:
.size _ZN3bar20h2bb2fd5b9c9e987beaaE, .Ltmp1-_ZN3bar20h2bb2fd5b9c9e987beaaE
.cfi_endproc
So you can see that the only thing it is calling out to is a helper routine, __morestack, that checks for stack-overflow (or allocate more stack, in systems with segmented stack support). (So for an example like this, that is the only core functionality you will need to provide yourself; note that you could just have it halt the kernel.)
Here is the program I put into the playpen:
#[inline(never)]
fn bar(f: fn() -> ((int, int, int), (int, int, int))) -> bool {
let (x, y) = f();
x == y
}
#[inline(never)]
fn foo_1() -> ((int,int,int), (int,int,int)) {
let x = (4i,2i,3i);
let y = (3i,4i,4i);
(x, y)
}
#[inline(never)]
fn foo_2() -> ((int,int,int), (int,int,int)) {
let x = (4i,2i,3i);
(x, x)
}
fn main() {
println!("bar(foo_1): {}", bar(foo_1));
println!("bar(foo_2): {}", bar(foo_2));
}
Rust had been designed to allow one to implement an operating system kernel, drivers or an application that does not even have an operating systems and runs on bare-metal hardware.
Currently Rust's standard runtime can be disable with #![no_std] attribute in the code. You can still use some libraries, such as libcore. One of the things that you will not get without runtime is format! and println! macros, the sprintf() and printf() equivalents.
For an example of something you can do today, take a look at Zinc project.

visual studio 2012 update 3 compiler bug - not calling dtor

I believe I've found a somewhat obscure but scary bug in the Visual Studio 2012 Update 3 C++ compiler. I found it while writing unit tests using gtest. The tests started showing memory leaks, and after investigating the problem seemed to reduce to a bug in the compiler.
I submitted the issue to Microsoft:
https://connect.microsoft.com/VisualStudio/feedback/details/794722/parameter-dtor-not-called-when-overloaded-operator-involved-in-return
In the past I've mistakenly called "compiler bug" on more of my own bugs than I care to admit. So I thought I'd post the question here in case anyone wants to attempt to reproduce the problem themselves. If I can be pointed towards a mistake of my own in this code, that would be extremely helpful! I'm really hoping it's not actually the case that the VC++ compiler fails to call destructors in the following program.
Note that the faulty behavior occurs with the optimizer disabled, so it's not an optimizer bug.
I tried this code in gcc 4.2.1 (i686-apple-darwin11) and it behaves as expected.
Here's the code for the single source file in the project:
#include <string>
int instance_count= 0;
class c {
public:
c( std::string s ) : m_s(s) { ++instance_count; }
c( const c& other ) : m_s(other.m_s) { ++instance_count; }
~c() {--instance_count;}
private:
std::string m_s;
};
class d {
public:
d() {}
void operator=(int) {}
};
void f( c c_ ) {
try {}
catch(...) { return d() = 5; }
}
int main( int argc, char* argv[] ) {
c instance("leak");
f(instance);
return instance_count == 1 ? 0 : -1;
}
To compile it in Visual Studio 2012 Update 3:
File -> New -> Project..., select Win32 Console Application, click OK then click Finish
Build -> Configuration Manager -> Active Solution Platform -> New..., select x64, click OK
Replace the contents of the main .cpp file with the above code
Either add #include "stdafx.h" to the top of the file or turn off precompiler headers
Run the program, note that the exit code is -1, I expect it to be 0. This seems to reproduce in both 32-bit and 64-bit builds, although I was focusing on 64-bit.
Comment out the try/catch blocks in f(), note that the exit code becomes 0. I don't see why this change should affect the exit code since the catch() block isn't even executing.
Looks like an issue in codegen. The dissassembly shows the following for function f.
With return statement -
try { }
002039B8 mov byte ptr [ebp-4],1
002039BC jmp f+6Eh (02039DEh)
catch(...) { return d() = 5; }
002039BE push 5
002039C0 lea ecx,[ebp-0D5h]
002039C6 call d::d (0201474h)
002039CB mov ecx,eax
002039CD call d::operator= (0201479h)
002039D2 mov eax,2039E7h
002039D7 ret
002039D8 mov eax,2039DEh
002039DD ret
$LN4:
002039DE mov dword ptr [ebp-4],0
002039E5 jmp $LN8+0Fh (02039F6h)
$LN8:
002039E7 mov dword ptr [ebp-4],0FFFFFFFFh
002039EE lea ecx,[c_]
002039F1 call c::~c (020101Eh)
}
Notice the jump f+6Eh(02039DEh) for dissassembly of try block. This jumps to
002039DE mov dword ptr [ebp-4],0
002039E5 jmp $LN8+0Fh (02039F6h)
which totally skips the call to destructor. One more thing to observe is that the call to destructor is before the closing brace ('}').
If we take a look at the code without return statement,
try { }
013839B8 mov byte ptr [ebp-4],1
013839BC jmp f+68h (013839D8h)
catch(...) { /*return*/ d() = 5; }
013839BE push 5
013839C0 lea ecx,[ebp-0D5h]
013839C6 call d::d (01381474h)
013839CB mov ecx,eax
013839CD call d::operator= (01381479h)
013839D2 mov eax,13839E1h
013839D7 ret
013839D8 mov dword ptr [ebp-4],0
013839DF jmp $LN8+7h (013839E8h)
$LN8:
013839E1 mov dword ptr [ebp-4],0
}
013839E8 mov dword ptr [ebp-4],0FFFFFFFFh
013839EF lea ecx,[c_]
013839F2 call c::~c (0138101Eh)
Here, the call to destructor is after the brace ('}').

Resources