llvm libLLVMCore源码分析 10 - Exception Handling Instructions

源码路径

llvm\include\llvm\IR\Instruction.h

llvm\include\llvm\IR\Instruction.def

llvm\include\llvm\IR\Instructions.h

llvm\include\llvm\IR\InstrTypes.h

LLVM中的异常处理

当LLVM的代码中抛出异常时,LLVM runtime会先尝试找到对应函数的异常帧(exception frame),如果是支持异常处理的语言(如C++),则异常帧中会包含异常处理表(exception table)的引用,描述怎么处理异常。

异常处理表是语言相关的,对C++语言,异常表由一系列代码区间(code range)组成,代码区间描述了当异常在这个区间发生时应该如何处理。典型地,包含了可以处理的异常对象(exception object)的类型和应该采取的行动(action),而行动通常是把控制流转移到降落垫(landing pad)。在降落垫中,选择器(selector)会接收到异常结构(exception structure)和1个异常类型相关的选择器值(selector value),这样选择器就可以决定使用哪个catch中的代码来处理当前的异常。

llvm支持3种类型的异常处理:

  • Itanium ABI异常处理(GCC):通过将异常相关指令和数据放到正常程序控制流之外,实现异常不发生时近似零开销的异常处理。
  • Setjmp/Longjmp异常处理:将异常处理代码融入正常程序控制流,优点是异常处理速度更快,缺点是异常不发生时也有额外的开销。
  • Windows运行时异常处理:与上述两者都不同,是Windows专属的异常处理机制。

本文后续内容主要介绍Itanium ABI异常处理,以及相关的llvm指令。

Itanium C++ ABI:异常处理

异常处理数据结构(Exception Handling Data Structures)

异常处理机制中,主要使用3种数据结构:

Unwind Table

start:函数起始地址

end:函数结束地址

info ptr:指向一个变长的Info Block,里面包含展开描述符和语言相关的数据区。

Info Block

v:版本号

ulen:unwind descriptor的长度。

Personality:指向Personality Routine的指针。

f:标志,可以通过如下代码获取,在C++中EHANDLER标志表示函数是否存在try-catch region,UHANDLER标志表示是否存在CleanUp Actions。

#define UNW_FLAG_MASK 0x0000f f f f 00000000L

#define UNW_FLAG_OSMASK 0x0000f 00000000000L

#define UNW_FLAG_EHANDLER(x) ((x) & 0x0000000100000000L)

#define UNW_FLAG_UHANDLER(x) ((x) & 0x0000000200000000L)

unwind descriptor:由一系列Records组成,Records分为3类:

  • region header records
  • descriptor records for prologue regions
  • descriptor records for body regions

这些描述组用于组合起来描述Prologue Region和Body Region,用于描述如何正确地进行栈展开。

语言相关的数据区(Language-specific data area)

tcnt:try/catch region table entry的数量

ccnt:cleanup action table entry的数量

try/catch region table entry:

  • start:region起始地址
  • end:region结束地址
  • catch:指向catch语句块的指针
  • handler:指向exception handler的指针

cleanup action table entry:

  • start:region起始地址
  • end:region结束地址
  • action:指向cleanup action的指针

异常处理框架(Exception Handling Framework)

Itanium架构下的异常处理如下所示,分为语言相关和语言无关的两个部分。

语言无关的部分负责:

  • 传递异常
  • 分发异常(回调Personality Routine)
  • 栈展开

语言相关的部分:在语言运行时库中提供个性化的过程(personality routine),配合语言无关部分,完成异常处理。

异常处理的过程需要顺序进行2次栈展开:

第1次:

  • Unwind library从当前栈帧中找到PC指针
  • Unwind library通过Unwind Table找到地址范围内的Personality Routine的地址。
  • Unwind library调用Personality Routine,完成:1)在当前栈帧中寻找能够处理异常的Handler,如果找到,则终止第1次栈展开。2)将第2次栈展开中,landing pad回调的handler和调用landing pad的参数回传给Unwind library。

第2次:

  • Unwind library在每一级栈帧中用使用Personality Routine设置的参数调用landing pad,landing pad逐级进行栈展开,恢复寄存器和栈状态,这部分代码由编译器后端生成。
  • 接着会进行try block出口处相关的清理工作,比如try block中自动变量的析构,这部分由编译器前端生成。
  • Exception Handler会根据异常类型生成一个Switch Value,如果在当前异常表中,没有匹配的Handler,则会执行Cleanup Actions,然后通过resume继续进行栈回退;否则会调用Handler进行处理,Handler会根据Switch Value选择对应的catch语句块进行处理。
  • 当Handler处理完成后,Unwind library会判断栈展开过程结束,控制流会转移到handler对应的try语句块的结尾处,结束异常处理。

例如如下代码:

try { foo(); }
	catch (TYPE1) { ... }
	catch (TYPE2) { buz(); }
	bar();

生成的汇编代码如下:

// In "Normal" area:
	foo(); // Call Attributes: Landing Pad L1, Action Record A1
	goto E1;
	...
	E1: // End Label
	bar();

	// In "Exception" area;
	L1: // Landing Pad label
	[Back-end generated "compensation" code]
	goto C1;

	C1: // Cleanup label
	[Front-end generated cleanup code, destructors, etc]
	[corresponding to exit of try { } block]
	goto S1;

	S1: // Switch label
	switch(SWITCH_VALUE_PAD_ARGUMENT)
	{
	    case 1: goto H1; // For TYPE1
	    case 2: goto H2; // For TYPE2
	    //...
	    default: goto X1;
	}

	X1:
	[Cleanup code corresponding to exit of scope]
	[enclosing the try block]
	_Unwind_Resume();

	H1: // Handler label
	adjusted_exception_ptr = __cxa_get_exception_ptr(exception);
	[Initialize catch parameter]
	__cxa_begin_catch(exception);
	[User code]
	goto R1;

	H2:
	adjusted_exception_ptr = __cxa_get_exception_ptr(exception);
	[Initialize catch parameter]
	__cxa_begin_catch(exception);
	[User code]
	buz(); // Call attributes: Landing pad L2, action record A2
	goto R1;

	R1: // Resume label:
	__cxa_end_catch();
	goto E1;

	L2:
	C2:
	// Make sure we cleanup the current exception
	__cxa_end_catch();

	X2:
	[Cleanup code corresponding to exit of scope]
	[enclosing the try block]
	_Unwind_Resume();

LLVM C++异常处理实现

对如下代码:

class MyException1 {
};

class MyException2 {
};

class AutoVar {
public:
        ~AutoVar() {
        }
};

void foo(int a) {
        if (a) {
                throw MyException1();
        } else {
                throw MyException2();
        }
}

void bar() {
}

void catch1(MyException1 &e) {
}

void catch2(MyException2 &e) {
}

void buz(int a) {
        try {
                AutoVar v;
                foo(a);
        }
        catch (MyException1 &e) {
                catch1(e);
        }
        catch (MyException2 &e) {
                catch2(e);
        }
        bar();
}

生成的IR中,foo函数如下:

; Function Attrs: noinline optnone
define dso_local void @_Z3fooi(i32) #0 {
  %2 = alloca i32, align 4
  store i32 %0, i32* %2, align 4
  %3 = load i32, i32* %2, align 4
  %4 = icmp ne i32 %3, 0
  br i1 %4, label %5, label %8

; <label>:5:                                      ; preds = %1
  %6 = call i8* @__cxa_allocate_exception(i64 1) #3
  %7 = bitcast i8* %6 to %class.MyException1*
  call void @__cxa_throw(i8* %6, i8* bitcast ({ i8*, i8* }* @_ZTI12MyException1 to i8*), i8* null) #4
  unreachable

; <label>:8:                                      ; preds = %1
  %9 = call i8* @__cxa_allocate_exception(i64 1) #3
  %10 = bitcast i8* %9 to %class.MyException2*
  call void @__cxa_throw(i8* %9, i8* bitcast ({ i8*, i8* }* @_ZTI12MyException2 to i8*), i8* null) #4
  unreachable
                                                  ; No predecessors!
  ret void
}

在foo函数中:

  • 先通过__cxa_allocate_exception申请了异常的内存
  • 再通过bitcast指令转换为对应的异常类型
  • 最后通过__cxa_throw抛出异常,进入unwind library

buz函数如下:

; Function Attrs: noinline optnone
define dso_local void @_Z3buzi(i32) #0 personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) {
  %2 = alloca i32, align 4
  %3 = alloca %class.AutoVar, align 1
  %4 = alloca i8*
  %5 = alloca i32
  %6 = alloca %class.MyException2*, align 8
  %7 = alloca %class.MyException1*, align 8
  store i32 %0, i32* %2, align 4
  %8 = load i32, i32* %2, align 4
  invoke void @_Z3fooi(i32 %8)
          to label %9 unwind label %10

; <label>:9:                                      ; preds = %1
  call void @_ZN7AutoVarD2Ev(%class.AutoVar* %3) #4
  br label %24

; <label>:10:                                     ; preds = %1
  %11 = landingpad { i8*, i32 }
          cleanup
          catch i8* bitcast ({ i8*, i8* }* @_ZTI12MyException1 to i8*)
          catch i8* bitcast ({ i8*, i8* }* @_ZTI12MyException2 to i8*)
  %12 = extractvalue { i8*, i32 } %11, 0
  store i8* %12, i8** %4, align 8
  %13 = extractvalue { i8*, i32 } %11, 1
  store i32 %13, i32* %5, align 4
  call void @_ZN7AutoVarD2Ev(%class.AutoVar* %3) #4
  br label %14

; <label>:14:                                     ; preds = %10
  %15 = load i32, i32* %5, align 4
  %16 = call i32 @llvm.eh.typeid.for(i8* bitcast ({ i8*, i8* }* @_ZTI12MyException1 to i8*)) #4
  %17 = icmp eq i32 %15, %16
  br i1 %17, label %18, label %25

; <label>:18:                                     ; preds = %14
  %19 = load i8*, i8** %4, align 8
  %20 = call i8* @__cxa_begin_catch(i8* %19) #4
  %21 = bitcast i8* %20 to %class.MyException1*
  store %class.MyException1* %21, %class.MyException1** %7, align 8
  %22 = load %class.MyException1*, %class.MyException1** %7, align 8
  invoke void @_Z6catch1R12MyException1(%class.MyException1* dereferenceable(1) %22)
          to label %23 unwind label %39

; <label>:23:                                     ; preds = %18
  call void @__cxa_end_catch()
  br label %24

; <label>:24:                                     ; preds = %23, %33, %9
  call void @_Z3barv()
  ret void

; <label>:25:                                     ; preds = %14
  %26 = call i32 @llvm.eh.typeid.for(i8* bitcast ({ i8*, i8* }* @_ZTI12MyException2 to i8*)) #4
  %27 = icmp eq i32 %15, %26
  br i1 %27, label %28, label %44

; <label>:28:                                     ; preds = %25
  %29 = load i8*, i8** %4, align 8
  %30 = call i8* @__cxa_begin_catch(i8* %29) #4
  %31 = bitcast i8* %30 to %class.MyException2*
  store %class.MyException2* %31, %class.MyException2** %6, align 8
  %32 = load %class.MyException2*, %class.MyException2** %6, align 8
  invoke void @_Z6catch2R12MyException2(%class.MyException2* dereferenceable(1) %32)
          to label %33 unwind label %34

; <label>:33:                                     ; preds = %28
  call void @__cxa_end_catch()
  br label %24

; <label>:34:                                     ; preds = %28
  %35 = landingpad { i8*, i32 }
          cleanup
  %36 = extractvalue { i8*, i32 } %35, 0
  store i8* %36, i8** %4, align 8
  %37 = extractvalue { i8*, i32 } %35, 1
  store i32 %37, i32* %5, align 4
  invoke void @__cxa_end_catch()
          to label %38 unwind label %49

; <label>:38:                                     ; preds = %34
  br label %44

; <label>:39:                                     ; preds = %18
  %40 = landingpad { i8*, i32 }
          cleanup
  %41 = extractvalue { i8*, i32 } %40, 0
  store i8* %41, i8** %4, align 8
  %42 = extractvalue { i8*, i32 } %40, 1
  store i32 %42, i32* %5, align 4
  invoke void @__cxa_end_catch()
          to label %43 unwind label %49

; <label>:43:                                     ; preds = %39
  br label %44

; <label>:44:                                     ; preds = %43, %38, %25
  %45 = load i8*, i8** %4, align 8
  %46 = load i32, i32* %5, align 4
  %47 = insertvalue { i8*, i32 } undef, i8* %45, 0
  %48 = insertvalue { i8*, i32 } %47, i32 %46, 1
  resume { i8*, i32 } %48

; <label>:49:                                     ; preds = %39, %34
  %50 = landingpad { i8*, i32 }
          catch i8* null
  %51 = extractvalue { i8*, i32 } %50, 0
  call void @__clang_call_terminate(i8* %51) #6
  unreachable
}


在buz函数中:

通过invoke指令调用foo,正常结束跳转到label9,调用AutoVar类变量v的析构函数,再跳转到label24,调用函数bar,正常退出;若有异常,则跳转到label10。

在label10中,通过landingpad指令获取异常对象的地址,存入变量4%,获取异常对象的typeid,存入变量5%,接着调用AutoVar类变量v的析构函数,完成清理工作,跳转到label14。

在label14中,通过@llvm.eh.typeid.for获取MyException1的typeid,并与捕获到的异常对象的typeid比较,如果相等,则跳转到label18。

在label18中,调用__cxa_begin_catch获取抛出的MyException1的对象指针,再通过invoke调用catch1进行处理,如果正常结束,跳转到label23。

在label23中,调用__cxa_end_catch结束异常处理,跳转到label24,调用函数bar,正常退出。

Exception Handling Instructions

从上文可以看出,llvm通过一系列指令(如landingpad)配合intrinsic(如llvm.eh.typeid.for),完成异常处理。

LandingPadInst(父类:Instruction)

landingpad指令用于说明所在的BasicBlock是一个异常降落垫。

语法

<resultval> = landingpad <resultty> <clause>+
<resultval> = landingpad <resultty> cleanup <clause>*

<clause> := catch <type> <value>
<clause> := filter <array constant type> <array constant>

参数说明:

resultty:Personality Routine设置的参数类型。

resultval:Personality Routine设置的类型为resultty的参数。

cleanup(可选):如果设置了cleanup,标识当前的BasicBlock是用于执行清理动作,通常会被执行;如果没有设置cleanup,标识当前的BasicBlock用于处理异常,既异常类型一定和landingpad的类型匹配。

catch <type> <value>:value为一个全局变量,表示当前landingpad可以捕获的异常类型(如@_ZTI12MyException1)。

filter <type> <value>:value为一个数组,表示当前landingpad可以抛出的异常类型。

语义

landingpad指令定义了Personality Routine设置的参数类型和值,是BasicBlock的第一个非PHI指令,用于提供异常降落垫。

示例

;; A landing pad which can catch an integer.
%res = landingpad { i8*, i32 }
         catch i8** @_ZTIi
;; A landing pad that is a cleanup.
%res = landingpad { i8*, i32 }
         cleanup
;; A landing pad which can catch an integer and can only throw a double.
%res = landingpad { i8*, i32 }
         catch i8** @_ZTIi
         filter [1 x i8**] [i8** @_ZTId]

 ResumeInst(父类:Instruction)

resume指令用于恢复异常传播。

语法

resume <type> <value>

语义

指令用于恢复被landingpad指令打断的异常传播过程(栈回退)。

示例

resume { i8*, i32 } %exn

CleanupPadInst(父类:FuncletPadInst)

用于WINDOWS运行时异常处理,不做介绍。

CleanupPad(父类:FuncletPadInst)

用于WINDOWS运行时异常处理,不做介绍。

CatchSwitchInst(父类:Instruction)

用于WINDOWS运行时异常处理,不做介绍。

CatchReturnInst(父类:Instruction)

用于WINDOWS运行时异常处理,不做介绍。

CleanupReturnInst(父类:Instruction)

用于WINDOWS运行时异常处理,不做介绍。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值