puerts / backend-v8

43 stars 49 forks source link

疑似v8在ue下特有的bug,有概率导致gc崩溃 #13

Closed chexiongsheng closed 4 months ago

chexiongsheng commented 4 months ago

以v9.4.146.24为例,其handlescope的block释放代码如下:

void HandleScopeImplementer::DeleteExtensions(internal::Address* prev_limit) {
  while (!blocks_.empty()) {
    internal::Address* block_start = blocks_.back();
    internal::Address* block_limit = block_start + kHandleBlockSize;

    // SealHandleScope may make the prev_limit to point inside the block.
    // Cast possibly-unrelated pointers to plain Addres before comparing them
    // to avoid undefined behavior.
    if (reinterpret_cast<Address>(block_start) <=
            reinterpret_cast<Address>(prev_limit) &&
        reinterpret_cast<Address>(prev_limit) <=
            reinterpret_cast<Address>(block_limit)) {
#ifdef ENABLE_HANDLE_ZAPPING
      internal::HandleScope::ZapRange(prev_limit, block_limit);
#endif
      break;
    }

    blocks_.pop_back();
#ifdef ENABLE_HANDLE_ZAPPING
    internal::HandleScope::ZapRange(block_start, block_limit);
#endif
    if (spare_ != nullptr) {
      DeleteArray(spare_);
    }
    spare_ = block_start;
  }
  DCHECK((blocks_.empty() && prev_limit == nullptr) ||
         (!blocks_.empty() && prev_limit != nullptr));
}

其判断是否要结束循环的条件是(prev_limit)地址是否在[block_start, block_limit]范围,但如果两个block刚好分配时粘在一起(一块的尾地址等于另外一块的首地址),就有可能提前满足条件(prev_limit等于block_start),导致漏释放了block,进而导致数据上的错乱,最终在gc时崩溃。

v8一直没发现可能是因为一般内存分配器都会在返回地址前留个header,不会出现两块内存粘一起的情况。而ue重载了new,它的内存分配会有这种情况。

chexiongsheng commented 4 months ago

其中一种修复办法:分配block时,多分配一个sizeof(internal::Address),浪费一点空间(每个block浪费1/1022),避免block间粘在一起。

chexiongsheng commented 4 months ago

补充下比较容易触发的场景:大量的HandleScope创建和销毁。比如JSON.parse解析超大json字符串。

chexiongsheng commented 4 months ago

如果C++规范没说不允许两块new的内存粘连,那这是v8的bug,否则这是ue的bug。

BlurryLight commented 4 months ago

这个情况下很容易直接触发v8::abort()...用 -ansimalloc可以绕开

zhaojunmeng commented 4 months ago

如果C++规范没说不允许两块new的内存粘连,那这是v8的bug,否则这是ue的bug。

https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2310.pdf 256页: 7.22.3 Memory management functions Each such allocation shall yield a pointer to an object disjoint from any other object.

看起来是后者

zhaojunmeng commented 3 months ago

把if语句中的 block_start <= prev_limit 改成 block_start < prev_limit 应该也能修复这个问题

我给V8官方提了一个修复: https://issues.chromium.org/issues/352446088