Skip to content

other_c2 bug

ceragon edited this page Dec 13, 2021 · 2 revisions

定位一个C2 编译导致的崩溃问题

问题回顾

  1. 当时崩溃日志的堆栈如下, 使用的是 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
  1. 由于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 的类型

  1. 首先定位下 参数 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);
    }
}
  1. 根据调用栈信息,继续定位 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);
    }
}
  1. 到这里基本可以确定 参数 o 是个方法,因为调用的是 get_method

代码出错位置

  1. 由于 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;
}
  1. 这里的 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 {
}
  1. 在对该对象有了大概的认识之后,大胆推测是因为要在 arena() 这块区域上分配这个对象,结果失败了,于是就崩溃了

arena() 的赋值

  1. arena 是在 ciObjectFactory 的构造函数中赋值的
ciObjectFactory::ciObjectFactory(Arena* arena, int expected_size) {
    _arena = arena;
}
  1. 查看 ciObjectFactory 的初始化位置, 先回顾下面这个个方法,因为这里是调用堆栈中最晚出现的 ciObjectFactory 的使用位置
ciMetadata* get_metadata(Metadata* o) {
    if (o == NULL) {
      return NULL;
    } else {
      return _factory->get_metadata(o);
    }
}
  1. _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);
}

arena 的创建

  1. 根据上一步定位出来的 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();
}
  1. 这里需要注意的是,Arena 的new操作符被重载了,所以实际构造过程如下:
void* Arena::operator new(size_t size, MEMFLAGS flags) throw() {
  return (void *) AllocateHeap(size, flags, CALLER_PC);
}

img.png

  1. 从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;
}
  1. 可以看到通过 malloc函数分配了 56字节的空间,

ciObjectFactory 的创建

  1. 继续查看ciEnv initial(arena);这行代码的执行内容
ciEnv::ciEnv(Arena* arena) : _ciEnv_arena(mtCompiler) {
    _arena   = arena;
    _factory = new (_arena) ciObjectFactory(_arena, 128);
}
  1. 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;
}

img_1.png

class ciObjectFactory : public ResourceObj {
}
  1. 继续查看 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;
    }
}
  1. 在 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; }

ciMethod 的初始化

  1. 再一次回顾疑似问题的代码
ciMetadata* ciObjectFactory::create_new_metadata(Metadata* o) {
    return new (arena()) ciMethod(h_m, holder);
}
  1. 这里的new 操作符重载,且是 placement new 的类型,所以实际调用的是下面的方法。其中 size 参数就是 ciMethod 对象的大小,我调试的结果是 176
void* ResourceObj::operator new(size_t size, Arena *arena) throw() {
  address res = (address)arena->Amalloc(size);
  return res;
}
  1. 再继续查看 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;
    }
}
  1. 继续查看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;
}
  1. 考虑到其他代码出问题的概率不高,所以深入查看 new Chunk 的代码
void* Chunk::operator new(size_t requested_size, size_t length) throw() {
  return CHeapObj::operator new(requested_size + length);
}
  1. 继续查看 CHeapObj 的 new 方法,最后调用了malloc
void* CHeapObj::operator new(size_t size) throw() {
  return (void *) malloc(size);
}

Clone this wiki locally