v8的Frame

本文详细探讨了JavaScript引擎V8中的帧(frame)概念,解释了帧在代码运行和编译期的角色,以及如何通过帧链表构建调用堆栈。V8在函数调用时创建帧,分为caller和callee两个阶段。通过Execution::InstantiateFunction函数举例,展示了V8如何构建帧。文章深入分析了V8的调用序列,包括JSEntryStub和ArgumentsAdaptorTrampoline,阐述了从C调用JavaScript函数的整个过程,涉及EntryFrame和ArgumentsAdaptorFrame的创建与作用。
摘要由CSDN通过智能技术生成
一.何为frame
frame是一个代码运行期的概念,同时,在编译期又会用到它,它是一个active record,它记录了函数当前运行的信息。我们使用IDE进行调试的时候,用到的call stack,就是通过frame链表得到的。简单来说,每次的函数调用就会在栈上形成一个frame,同时该frame会连接到frame链表中成为当前的frame。一般来说,cpu会有一个专门的寄存器来保存当前frame的地址,在intel cpu中使用ebp。
frame可能包括的内容有:
1.parameters
2.return address
3.previous frame address
4.local variables
需要说明的是,frame并不是一次就创建好的,它的创建分为两部分
1.caller在函数调用前,要把参数分别入栈,然后把调用完成后的返回地址入栈,然后跳转到函数首地址
2.callee首先把frame pointer入栈,同时把sp赋值给fp,这样就把当前frame加入到frame链表中,然后再构建当前函数需要加入到frame中的内容
在函数中只需要通过fp及其偏移量就可以访问frame中的内容。
frame的格式和内容是与CPU,编译器、编译语言相关的,他们是一种convention,例如C/C++就有自己的ABI,但是不同的编译器,不同的CPU的ABI又不完全相同,也就是说,我们完全可以定义自己的frame,只要编译器,调试器能够识别就行,v8就是这么做的

二.v8的frame
我们以execution.cc中的Execution::InstantiateFunction为例,说明v8是如何构建frame的。

Handle<JSFunction> Execution::InstantiateFunction(
    Handle<FunctionTemplateInfo> data,
    bool* exc) {
  Isolate* isolate = data->GetIsolate();
  // Fast case: see if the function has already been instantiated
  int serial_number = Smi::cast(data->serial_number())->value();
  Object* elm =
      isolate->native_context()->function_cache()->
          GetElementNoExceptionThrown(serial_number);
  if (elm->IsJSFunction()) return Handle<JSFunction>(JSFunction::cast(elm));
  // The function has not yet been instantiated in this context; do it.
  Handle<Object> args[] = { data };
  Handle<Object> result = Call(isolate->instantiate_fun(),
                               isolate->js_builtins_object(),
                               ARRAY_SIZE(args),
                               args,
                               exc);
  if (*exc) return Handle<JSFunction>::null();
  return Handle<JSFunction>::cast(result);
}
其中重点是Call函数,如下:
Handle<Object> Execution::Call(Handle<Object> callable,
                               Handle<Object> receiver,
                               int argc,
                               Handle<Object> argv[],
                               bool* pending_exception,
                               bool convert_receiver) {
  *pending_exception = false;

  if (!callable->IsJSFunction()) {
    callable = TryGetFunctionDelegate(callable, pending_exception);
    if (*pending_exception) return callable;
  }
  Handle<JSFunction> func = Handle<JSFunction>::cast(callable);

  。。。。

  return Invoke(false, func, receiver, argc, argv, pending_exception);
}
Invoke
重点是Invoke函数,如下:
static Handle<Object> Invoke(bool is_construct,
                             Handle<JSFunction> function,
                             Handle<Object> receiver,
                             int argc,
                             Handle<Object> args[],
                             bool* has_pending_exception) {
  Isolate* isolate = function->GetIsolate();

  // Entering JavaScript.
  VMState state(isolate, JS);

  // Placeholder for return value.
  MaybeObject* value = reinterpret_cast<Object*>(kZapValue);

  typedef Object* (*JSEntryFunction)(byte* entry,
                                     Object* function,
                                     Object* receiver,
                                     int argc,
                                     Object*** args);

  <1>isolate->factory()->js_entry_code()和isolate->factory()->js_construct_entry_code()
  Handle<Code> code = is_construct
      ? isolate->factory()->js_construct_entry_code()
      : isolate->factory()->js_entry_code();

  // Convert calls on global objects to be calls on the global
  // receiver instead to avoid having a 'this' pointer which refers
  // directly to a global object.
  if (receiver->IsGlobalObject()) {
    Handle<GlobalObject> global = Handle<GlobalObject>::cast(receiver);
    receiver = Handle<JSObject>(global->global_receiver());
  }

  // Make sure that the global object of the context we're about to
  // make the current one is indeed a global object.
  ASSERT(function->context()->global_object()->IsGlobalObject());

  {
    // Save and restore context around invocation and block the
    // allocation of handles without explicit handle scopes.
    SaveContext save(isolate);
    NoHandleAllocation na(isolate);
    JSEntryFunction stub_entry = FUNCTION_CAST<JSEntryFunction>(code->entry());

    // Call the function through the right JS entry stub.
    byte* function_entry = function->code()->entry();
    JSFunction* func = *function;
    Object* recv = *receiver;
    Object*** argv = reinterpret_cast<Object***>(args);
    value =
        <2>#define CALL_GENERATED_CODE(entry, p0, p1, p2, p3, p4) \
  (entry(p0, p1, p2, p3, p4))

        CALL_GENERATED_CODE(stub_entry, function_entry, func, recv, argc, argv);
  }

#ifdef VERIFY_HEAP
  value->Verify();
#endif

  // Update the pending exception flag and return the value.
  *has_pending_exception = value->IsException();
  ASSERT(*has_pending_exception == isolate->has_pending_exception());
  if (*has_pending_exception) {
    isolate->ReportPendingMessages();
    if (isolate->pending_exception()->IsOutOfMemory()) {
      if (!isolate->ignore_out_of_memory()) {
        V8::FatalProcessOutOfMemory("JS", true);
      }
    }
#ifdef ENABLE_DEBUGGER_SUPPORT
    // Reset stepping state when script exits with uncaught exception.
    if (isolate->debugger()->IsDebuggerActive()) {
      isolate->debug()->ClearStepping();
    }
#endif  // ENABLE_DEBUGGER_SUPPORT
    return Handle<Object>();
  } else {
    isolate->clear_pending_message();
  }

  return Handle<Object>(value->ToObjectUnchecked(), isolate);
}
js_entry_code()
下面我们分析上述的调用序列
<1>isolate->factory()->js_entry_code()和isolate->factory()->js_construct_entry_code()
factory->js_entry_code()函数是在factory.h中通过如下的宏定义的
#define ROOT_ACCESSOR(type, name, camel_name)                                  \
  inline Handle<type> name() {                                                 \
    return Handle<type>(BitCast<type**>(                                       \
        &isolate()->heap()->roots_[Heap::k##camel_name##RootIndex]));          \
  }
  ROOT_LIST(ROOT_ACCESSOR)
ROOT_LIST是在heap.h中定义的,其中
V(Code, js_entry_code, JsEntryCode)                                          \
V(Code, js_construct_entry_code, JsConstructEntryCode)                       \
也就是说,该函数返回的是heap中的roots_数组中的Code对象,参看 Heap RootObject的初始化,我们可以知道js_entry_code对象是在 Heap :: CreateJSEntryStub ()函数中创建的
void Heap :: CreateJSEntryStub () {
  JSEntryStub stub ;
  set_js_entry_code (* stub . GetCode ( isolate ()));
}
JSEntryStub是CodeStub类的派生类,CodeStub定义了GetCode的流程,JSEntryStub实现GenerateBody虚函数,所以重点在于JSEntryStub::GenerateBody函数的实现
void JSEntryStub :: GenerateBody ( MacroAssembler * masm , bool is_construct ) {
  Label invoke , handler_entry , exit ;
  Label not_outermost_js , not_outermost_js_2 ;

  // Set up frame.
  //这里构建frame的后半部分
  __ push ( ebp );
  __ mov ( ebp , esp );

  // Push marker in two places.
  int marker = is_construct ? StackFrame :: ENTRY_CONSTRUCT : StackFrame :: ENTRY ;
  //marker用来表示这是一个frame的类型,同时它又起到占位符的作用,可以作为context和function的slot
  __ push ( Immediate ( Smi :: FromInt ( marker )));  // context slot
  __ push ( Immediate ( Smi :: FromInt ( marker )));  // function slot
  // Save callee-saved registers (C calling conventions).
  __ push ( edi );
  __ push ( esi );
  __ push ( ebx );

  // Save copies of the top frame descriptor on the stack.
  <1>ExternalReference c_entry_fp
  ExternalReference c_entry_fp ( Isolate :: kCEntryFPAddress , masm -> isolate ());
  __ push ( Operand :: StaticVariable ( c_entry_fp ));

  // If this is the outermost JS call, set js_entry_sp value.
  ExternalReference js_entry_sp ( Isolate :: kJSEntrySPAddress ,
                                masm -> isolate ());
  //判断js_entry_sp的内容是否为null
  __ cmp ( Operand :: StaticVariable ( js_entry_sp ), Immediate (0));
  //如果不是null,跳转到not_outmost_js
  __ j ( not_equal , & not_outermost_js , Label :: kNear );
  //如果是null,说明是outmost js entry
  //把ebp赋值给js_entry_sp
  __ mov ( Operand :: StaticVariable ( js_entry_sp ), ebp );
  //push一个marker表示这是一个outmost_jsentry_frame
  __ push ( Immediate ( Smi :: FromInt ( StackFrame :: OUTERMOST_JSENTRY_FRAME )));
  //跳转到invoke
  __ jmp (& invoke , Label :: kNear );
  __ bind (& not_outermost_js );
  //not _outmost标签
  //push一个marker表示这是一个inner_jsentry_frame
  __ push ( Immediate ( Smi :: FromInt ( StackFrame :: INNER_JSENTRY_FRAME )));

  // Jump to a faked try block that does the invoke, with a faked catch
  // block that sets the pending exception.
  //跳转到invoke标签
  __ jmp (& invoke );
  __ bind (& handler_entry );
  handler_offset_ = handler_entry . pos ();
  // Caught exception: Store result (exception) in the pending exception
  // field in the JSEnv and return a failure sentinel.
  ExternalReference pending_exception ( Isolate :: kPendingExceptionAddress ,
                                      masm -> isolate ());
  __ mov ( Operand :: StaticVariable ( pending_exception ), eax );
  __ mov ( eax , reinterpret_cast < int32_t >( Failure :: Exception ()));
  __ jmp (& exit );

  // Invoke: Link this frame into the handler chain.  There's only one
  // handler block in this code object, so its index is 0.
  __ bind (& invoke );
  //这里应该是用来异常处理的,还需要进一步分析
  __ PushTryHandler ( StackHandler :: JS_ENTRY , 0);

  // Clear any pending exceptions.
  __ mov ( edx , Immediate ( masm -> isolate ()-> factory ()-> the_hole_value ()));
  __ mov ( Operand :: StaticVariable ( pending_exception ), edx );

  // Fake a receiver (NULL).
  __ push ( Immediate (0));  // receiver

  // Invoke the function by calling through JS entry trampoline builtin and
  // pop the faked function when we return. Notice that we cannot store a
  // reference to the trampoline code directly in this stub, because the
  // builtin stubs may not have been generated yet.
  if ( is_construct ) {
    ExternalReference construct_entry ( Builtins :: kJSConstructEntryTrampoline ,
                                      masm -> isolate ());
    __ mov ( edx , Immediate ( construct_entry ));
  } else {
   <2>ExternalReference entry ( Builtins:: kJSEntryTrampoline
    ExternalReference entry ( Builtins :: kJSEntryTrampoline ,
                            masm -> isolate ());
    //mov指令执行后,edx中存储的是Code**
    __ mov ( edx , Immediate ( entry ));
  }
  //Operand(edx , 0)对edx进行接引用,mov指令执行后,edx存储的是Code*
  __ mov ( edx , Operand ( edx , 0));  // deref address
  //lea指令是把源操作数的有效地址,即偏移量存储在指定的寄存器中,这里edx+Code::kHeaderSize就是有效地址,它被存储在edx中
  __ lea ( edx , FieldOperand ( edx , Code :: kHeaderSize ));
  __ call ( edx );

  // Unlink this frame from the handler chain.
  __ PopTryHandler ();

  __ bind (& exit );
  // Check if the current stack frame is marked as the outermost JS frame.
  __ pop ( ebx );
  __ cmp ( ebx , Immediate ( Smi :: FromInt ( StackFrame :: OUTERMOST_JSENTRY_FRAME )));
  __ j ( not_equal , & not_outermost_js_2 );
  __ mov ( Operand :: StaticVariable ( js_entry_sp ), Immediate (0));
  __ bind (& not_outermost_js_2 );

  // Restore the top frame descriptor from the stack.
  __ pop ( Operand :: StaticVariable ( ExternalReference (
      Isolate :: kCEntryFPAddress ,
      masm -> isolate ())));

  // Restore callee-saved registers (C calling conventions).
  __ pop ( ebx );
  __ pop ( esi );
  __ pop ( edi );
  __ add ( esp , Immediate (2 * kPointerSize ));  // remove markers

  // Restore frame pointer and return.
  __ pop ( ebp );
  __ ret (0);
}
下面分别对标号1和2进行说明
<1>ExternalReference c_entry_fp Isolate:: kCEntryFPAddress
关于ExternalReference类的注释如下:
// An ExternalReference represents a C++ address used in the generated
// code. All references to C++ functions and variables must be encapsulated in
// an ExternalReference instance. This is done in order to track the origin of
// all external references in the code so that they can be bound to the correct
// addresses when deserializing a heap.
通俗来说,在构建该类的时候,传入不同的类型,可以得到一个地址,这个转换过程是被ExternalReference封装的,对于Isolate::kCEntryFPAddress而言,它对应的地址是Isolate:: isolate_addresses_[]数组中一个元素,该数组是在Isolate::init函数中初始化的
#define ASSIGN_ELEMENT ( CamelName , hacker_name )                  \
  isolate_addresses_ [ Isolate :: k # # CamelName # # Address ] =          \
      reinterpret_cast < Address >( hacker_name # # _address ());
  FOR_EACH_ISOLATE_ADDRESS_NAME ( ASSIGN_ELEMENT )
其中 FOR_EACH_ISOLATE_ADDRESS_NAME 的定义如下:
#define FOR_EACH_ISOLATE_ADDRESS_NAME ( C )                \
  C ( Handler , handler )                                   \
  C
你可以使用以下代码片段来使用YOLO v8模型读取网络摄像头中的视频流: ```python import cv2 import numpy as np # 加载YOLO v3配置和权重文件 net = cv2.dnn.readNetFromDarknet('yolov3.cfg', 'yolov3.weights') # 获取输出层的名称 layer_names = net.getLayerNames() output_layers = [layer_names[i[0] - 1] for i in net.getUnconnectedOutLayers()] # 打开网络摄像头 cap = cv2.VideoCapture(0) while True: # 读取视频帧 ret, frame = cap.read() # 对帧进行预处理 blob = cv2.dnn.blobFromImage(frame, 0.00392, (416, 416), (0, 0, 0), True, crop=False) net.setInput(blob) outs = net.forward(output_layers) # 解析输出并绘制边界框 class_ids = [] confidences = [] boxes = [] height, width, channels = frame.shape for out in outs: for detection in out: scores = detection[5:] class_id = np.argmax(scores) confidence = scores[class_id] if confidence > 0.5: # 检测到对象 center_x = int(detection[0] * width) center_y = int(detection[1] * height) w = int(detection[2] * width) h = int(detection[3] * height) # 边界框的坐标 x = int(center_x - w / 2) y = int(center_y - h / 2) boxes.append([x, y, w, h]) confidences.append(float(confidence)) class_ids.append(class_id) indexes = cv2.dnn.NMSBoxes(boxes, confidences, 0.5, 0.4) font = cv2.FONT_HERSHEY_SIMPLEX for i in range(len(boxes)): if i in indexes: x, y, w, h = boxes[i] label = str(class_ids[i]) confidence = confidences[i] color = (255, 0, 0) # 边界框颜色为红色 cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2) cv2.putText(frame, label + ' ' + str(round(confidence, 2)), (x, y - 10), font, 0.5, color, 2) # 显示输出图像 cv2.imshow('YOLO v3', frame) # 按下q键退出循环 if cv2.waitKey(1) & 0xFF == ord('q'): break # 释放资源 cap.release() cv2.destroyAllWindows() ``` 请确保你已经下载了YOLO v3的配置文件(`yolov3.cfg`)和权重文件(`yolov3.weights`),并将其放在与你的代码相同的目录下。此代码将从网络摄像头中读取视频流,并在检测到的对象周围绘制边界框。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值