Repository navigation
other_c2 bug
ceragon edited this page Dec 13, 2021
·
2 revisions
- 当时崩溃日志的堆栈如下, 使用的是 jdk1.8 版本
Stack: [0x00007f5fa3216000,0x00007f5fa3317000], sp=0x00007f5fa3313670, free space=1013k
Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)
V [libjvm.so+0x43c477] ciObjectFactory::create_new_metadata(Metadata*)+0x327
V [libjvm.so+0x43c8a5] ciObjectFactory::get_metadata(Metadata*)+0x85
V [libjvm.so+0x434397] ciSpeculativeTrapData::translate_from(ProfileData const*)+0x67
V [libjvm.so+0x4346a8] ciMethodData::load_extra_data()+0xa8
V [libjvm.so+0x436828] ciMethodData::load_data()+0x268
V [libjvm.so+0x4295a4] ciMethod::method_data()+0xb4
V [libjvm.so+0x959175] Parse::Parse(JVMState*, ciMethod*, float)+0x645
V [libjvm.so+0x3f6ed8] ParseGenerator::generate(JVMState*)+0x88
V [libjvm.so+0x5709eb] Parse::do_call()+0x23b
V [libjvm.so+0x9628e2] Parse::do_one_bytecode()+0x3382
V [libjvm.so+0x953688] Parse::do_one_block()+0x178
V [libjvm.so+0x9579b7] Parse::do_all_blocks()+0x127
V [libjvm.so+0x959379] Parse::Parse(JVMState*, ciMethod*, float)+0x849
V [libjvm.so+0x3f6ed8] ParseGenerator::generate(JVMState*)+0x88
V [libjvm.so+0x4a959a] Compile::Compile(ciEnv*, C2Compiler*, ciMethod*, int, bool, bool, bool)+0x127a
V [libjvm.so+0x3f5895] C2Compiler::compile_method(ciEnv*, ciMethod*, int)+0x1f5
V [libjvm.so+0x4b3d4a] CompileBroker::invoke_compiler_on_method(CompileTask*)+0xcba
V [libjvm.so+0x4b4cf6] CompileBroker::compiler_thread_loop()+0x5d6
V [libjvm.so+0xa7f243] JavaThread::thread_main_inner()+0x103
V [libjvm.so+0xa7f38c] JavaThread::run()+0x11c
V [libjvm.so+0x92e0f8] java_start(Thread*)+0x108
C [libpthread.so.0+0x79d1] start_thread+0xd1
- 由于openjdk1.8 无法使用clion,所以暂以 openjdk11 来尝试分析一下, 出错的代码的方法如下
ciMetadata* ciObjectFactory::create_new_metadata(Metadata* o) {
EXCEPTION_CONTEXT;
if (o->is_klass()) {
Klass* k = (Klass*)o;
if (k->is_instance_klass()) {
return new (arena()) ciInstanceKlass(k);
} else if (k->is_objArray_klass()) {
return new (arena()) ciObjArrayKlass(k);
} else if (k->is_typeArray_klass()) {
return new (arena()) ciTypeArrayKlass(k);
}
} else if (o->is_method()) {
methodHandle h_m(THREAD, (Method*)o);
ciEnv *env = CURRENT_THREAD_ENV;
ciInstanceKlass* holder = env->get_instance_klass(h_m()->method_holder());
return new (arena()) ciMethod(h_m, holder);
} else if (o->is_methodData()) {
// Hold methodHandle alive - might not be necessary ???
methodHandle h_m(THREAD, ((MethodData*)o)->method());
return new (arena()) ciMethodData((MethodData*)o);
}
// The Metadata* is of some type not supported by the compiler interface.
ShouldNotReachHere();
return NULL;
}- 首先定位下 参数 o 属于哪个类型, 这得从调用过程入手,调用方法如下
下面贴出的代码只包含我认为重要的部分, 否则代码量太大
class ciObjectFactory : public ResourceObj {
}
ciMetadata* ciObjectFactory::get_metadata(Metadata* key) {
if (!found) {
// The ciMetadata does not yet exist. Create it and insert it
// into the cache.
ciMetadata* new_object = create_new_metadata(key);
}
}- 根据调用栈信息,继续定位 get_metadata 的调用方
void ciSpeculativeTrapData::translate_from(const ProfileData* data) {
Method* m = data->as_SpeculativeTrapData()->method();
ciMethod* ci_m = CURRENT_ENV->get_method(m);
set_method(ci_m);
}- 这是 CURRENT_ENV 的宏定义
#define CURRENT_ENV ciEnv::current()- CURRENT_ENV->get_method 的实际调用是
ciMethod* get_method(Method* o) {
if (o == NULL) return NULL;
return get_metadata(o)->as_method();
}
ciMetadata* get_metadata(Metadata* o) {
if (o == NULL) {
return NULL;
} else {
return _factory->get_metadata(o);
}
}- 到这里基本可以确定 参数 o 是个方法,因为调用的是 get_method
- 由于 o 是个方法,且排除其他代码报错的可能,暂时认定问题出在 new 的地方
ciMetadata* ciObjectFactory::create_new_metadata(Metadata* o) {
if (o->is_method()) {
methodHandle h_m(THREAD, (Method*)o);
ciEnv *env = CURRENT_THREAD_ENV;
ciInstanceKlass* holder = env->get_instance_klass(h_m()->method_holder());
// 出错位置!!!!
return new (arena()) ciMethod(h_m, holder);
}
return null;
}- 这里的 new 的写法是 placement new, 所以ciMethod这个对象会被分配在已创建好的 arena() 这块内存里。由于推测是对象创建引发的错误,所有有必要看下 ciMethod 类的成员变量,估算一下内存空间占用
class ciMethod : public ciMetadata {
private:
// General method information.
ciFlags _flags;
ciSymbol* _name;
ciInstanceKlass* _holder;
ciSignature* _signature;
ciMethodData* _method_data;
ciMethodBlocks* _method_blocks;
// Code attributes.
int _code_size;
int _max_stack;
int _max_locals;
vmIntrinsics::ID _intrinsic_id;
int _handler_count;
int _nmethod_age;
int _interpreter_invocation_count;
int _interpreter_throwout_count;
int _instructions_size;
int _size_of_parameters;
bool _uses_monitors;
bool _balanced_monitors;
bool _is_c1_compilable;
bool _is_c2_compilable;
bool _can_be_parsed;
bool _can_be_statically_bound;
bool _has_reserved_stack_access;
// Lazy fields, filled in on demand
address _code;
ciExceptionHandler** _exception_handlers;
// Optional liveness analyzer.
MethodLiveness* _liveness;
#if defined(COMPILER2)
ciTypeFlow* _flow;
BCEscapeAnalyzer* _bcea;
#endif
}
class ciMetadata: public ciBaseObject {
protected:
Metadata* _metadata;
}
class ciBaseObject : public ResourceObj {
protected:
uint _ident;
};
class ResourceObj ALLOCATION_SUPER_CLASS_SPEC {
private:
uintptr_t _allocation_t[2];
};
#define ALLOCATION_SUPER_CLASS_SPEC : public AllocatedObj
class AllocatedObj {
}- 在对该对象有了大概的认识之后,大胆推测是因为要在 arena() 这块区域上分配这个对象,结果失败了,于是就崩溃了
- arena 是在 ciObjectFactory 的构造函数中赋值的
ciObjectFactory::ciObjectFactory(Arena* arena, int expected_size) {
_arena = arena;
}- 查看 ciObjectFactory 的初始化位置, 先回顾下面这个个方法,因为这里是调用堆栈中最晚出现的 ciObjectFactory 的使用位置
ciMetadata* get_metadata(Metadata* o) {
if (o == NULL) {
return NULL;
} else {
return _factory->get_metadata(o);
}
}- _factory 的赋值如下, 但通过查看出错的堆栈信息,定位出使用的是上面的那个方法。定位env的构造
ciEnv::ciEnv(Arena* arena) : _ciEnv_arena(mtCompiler) {
_arena = arena;
_factory = new (_arena) ciObjectFactory(_arena, 128);
}ciEnv::ciEnv(CompileTask* task, int system_dictionary_modification_counter)
: _ciEnv_arena(mtCompiler) {
_arena = &_ciEnv_arena;
_factory = new (_arena) ciObjectFactory(_arena, 128);
}- 根据上一步定位出来的 ciEnv 对象的构造位置,找到以下代码
void ciObjectFactory::initialize() {
// This Arena is long lived and exists in the resource mark of the
// compiler thread that initializes the initial ciObjectFactory which
// creates the shared ciObjects that all later ciObjectFactories use.
Arena* arena = new (mtCompiler) Arena(mtCompiler);
ciEnv initial(arena);
ciEnv* env = ciEnv::current();
}- 这里需要注意的是,Arena 的new操作符被重载了,所以实际构造过程如下:
void* Arena::operator new(size_t size, MEMFLAGS flags) throw() {
return (void *) AllocateHeap(size, flags, CALLER_PC);
}
- 从debug的截图信息可知, size 的值是56,对应的就是Arena类对象占用空间的大小,继续查看 AllocateHeap
char* AllocateHeap(size_t size,
MEMFLAGS flags,
const NativeCallStack& stack,
AllocFailType alloc_failmode /* = AllocFailStrategy::EXIT_OOM*/) {
char* p = (char*) os::malloc(size, flags, stack);
return p;
}- 可以看到通过 malloc函数分配了 56字节的空间,
- 继续查看ciEnv initial(arena);这行代码的执行内容
ciEnv::ciEnv(Arena* arena) : _ciEnv_arena(mtCompiler) {
_arena = arena;
_factory = new (_arena) ciObjectFactory(_arena, 128);
}- new (_arena) ciObjectFactory(_arena, 128); 会执行下面的方法。原因是 ciObjectFactory 继承自 ResourceObj,而 ResourceObj 的 new 被重载了,且优先调用了父类的构造方法
void* ResourceObj::operator new(size_t size, Arena *arena) throw() {
address res = (address)arena->Amalloc(size);
return res;
}
class ciObjectFactory : public ResourceObj {
}- 继续查看 Amalloc 这个方法
void* Amalloc(size_t x, AllocFailType alloc_failmode = AllocFailStrategy::EXIT_OOM) {
// x = 584
x = ARENA_ALIGN(x);
// x = 592 , 此时 _max - _hwm = 984
if (_hwm + x > _max) {
return grow(x, alloc_failmode);
} else {
char *old = _hwm;
_hwm += x;
return old;
}
}- 在 ResourceObj 构造结束后, 下面的方法被调用。可以看到,在出问题的代码那里用到的 arena(),就是就是 arena 创建
ciObjectFactory::ciObjectFactory(Arena* arena, int expected_size) {
_arena = arena;
_ci_metadata = new (arena) GrowableArray<ciMetadata*>(arena, expected_size, 0, NULL);
// 此时 arena 剩余
}
Arena* arena() { return _arena; }- 再一次回顾疑似问题的代码
ciMetadata* ciObjectFactory::create_new_metadata(Metadata* o) {
return new (arena()) ciMethod(h_m, holder);
}- 这里的new 操作符重载,且是 placement new 的类型,所以实际调用的是下面的方法。其中 size 参数就是 ciMethod 对象的大小,我调试的结果是 176
void* ResourceObj::operator new(size_t size, Arena *arena) throw() {
address res = (address)arena->Amalloc(size);
return res;
}- 再继续查看 Amalloc 的方法,其中 check_for_overflow 肯定不会出问题。
bool check_for_overflow(size_t request, const char* whence,
AllocFailType alloc_failmode = AllocFailStrategy::EXIT_OOM) const {
// UINTPTR_MAX 的值是 指针的最大值,很明显指针怎么都不可能那么大
// UINTPTR_MAX = (18446744073709551615UL)
if (UINTPTR_MAX - request < (uintptr_t)_hwm) {
if (alloc_failmode == AllocFailStrategy::RETURN_NULL) {
return false;
}
signal_out_of_memory(request, whence);
}
return true;
}
void* Amalloc(size_t x, AllocFailType alloc_failmode = AllocFailStrategy::EXIT_OOM) {
x = ARENA_ALIGN(x);
if (!check_for_overflow(x, "Arena::Amalloc", alloc_failmode))
return NULL;
if (_hwm + x > _max) {
return grow(x, alloc_failmode);
} else {
char *old = _hwm;
_hwm += x;
return old;
}
}- 继续查看grow的方法内容,其中 Chunk::size 是 32 * 1024 - 40
void* Arena::grow(size_t x, AllocFailType alloc_failmode) {
size_t len = MAX2(x, (size_t) Chunk::size);
Chunk *k = _chunk; // Get filled-up chunk address
_chunk = new (alloc_failmode, len) Chunk(len);
if (_chunk == NULL) {
_chunk = k; // restore the previous value of _chunk
return NULL;
}
if (k) k->set_next(_chunk); // Append new chunk to end of linked list
else _first = _chunk;
_hwm = _chunk->bottom(); // Save the cached hwm, max
_max = _chunk->top();
set_size_in_bytes(size_in_bytes() + len);
void* result = _hwm;
_hwm += x;
return result;
}- 考虑到其他代码出问题的概率不高,所以深入查看 new Chunk 的代码
void* Chunk::operator new(size_t requested_size, size_t length) throw() {
return CHeapObj::operator new(requested_size + length);
}- 继续查看 CHeapObj 的 new 方法,最后调用了malloc
void* CHeapObj::operator new(size_t size) throw() {
return (void *) malloc(size);
}