【推理引擎】从源码看ONNXRuntime的执行流程
前言
在上一篇博客中:【推理引擎】ONNXRuntime 的架构设计,主要从文档上对ONNXRuntime的执行流程进行了梳理,但是想要深入理解,还需从源码角度进行分析。
本文以目标检测模型NanoDet作为分析的基础,部分代码主要参考:超轻量级NanoDet MNN/TNN/NCNN/ONNXRuntime C++工程记录 - DefTruth的文章 - 知乎,在此表示感谢!
准备工作
OrtHandlerBase
是用来操控 ONNXRuntime 的基类,各种网络模型都可以通过继承该类进而拥有 ONNXRuntime 的使用权限,比如 NanoDet
;同时,NanoDet
还可以扩展独属于自己的方法和成员变量,以方便推理前后的预处理和后处理工作。
构造NanoDet
对象时,会自动调用OrtHandlerBase
的构造方法,在构造方法内部会首先初始化一些必要的成员变量(Ort::Env
、Ort::SessionOptions
),这两个变量主要用于初始化 Ort::Session
:
ort_env = Ort::Env(ORT_LOGGING_LEVEL_ERROR, log_id);
Ort::SessionOptions session_options;
session_options.SetIntraOpNumThreads(num_threads);
session_options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_ENABLE_ALL);
session_options.SetLogSeverityLevel(4);
ort_session = new Ort::Session(ort_env, onnx_model_path, session_options);
构造 InferenceSession 对象 & 初始化
在构造 Ort::Session
对象的过程中,会调用ONNXRuntime -> onnxruntime_cxx_inline.h 中的API:
// include/onnxruntime/core/session/onnxruntime_cxx_inline.h
inline Session::Session(Env& env, const ORTCHAR_T* model_path, const SessionOptions& options) {
ThrowOnError(GetApi().CreateSession(env, model_path, options, &p_));
}
GetApi() 是在 onnxruntime_cxx_api.h 中定义的:
// include/onnxruntime/core/session/onnxruntime_cxx_api.h
// This returns a reference to the OrtApi interface in use
inline const OrtApi& GetApi() { return *Global<void>::api_; }
// 其中 Global 的定义如下:
template <typename T>
struct Global {
static const OrtApi* api_;
};
这里面主要定义了静态常量指针OrtApi*
,OrtApi
是在 onnxruntime_c_api.h 中定义的:
// include/onnxruntime/core/session/onnxruntime_c_api.h
// All C API functions are defined inside this structure as pointers to functions.
// Call OrtApiBase::GetApi to get a pointer to it
struct OrtApi;
typedef struct OrtApi OrtApi;
struct OrtApi{
...
// 以 CreateSession 为例:
ORT_API2_STATUS(CreateSession, _In_ const OrtEnv* env, _In_ const ORTCHAR_T* model_path,
_In_ const OrtSessionOptions* options, _Outptr_ OrtSession** out);
// 展开ORT_API2_STATUS宏:
// _Check_return_ _Ret_maybenull_ OrtStatusPtr(ORT_API_CALL* CreateSession)(const OrtEnv* env,
// const char* model_path,
// const OrtSessionOptions* options,
// OrtSession** out) NO_EXCEPTION ORT_MUST_USE_RESULT;
...
}
相应地,在 onnxruntime_c_api.cc 文件中定义了 CreateSesssion 的实现:
// onnxruntime/core/session/onnxruntime_c_api.cc
ORT_API_STATUS_IMPL(OrtApis::CreateSession, _In_ const OrtEnv* env, _In_ const ORTCHAR_T* model_path,
_In_ const OrtSessionOptions* options, _Outptr_ OrtSession** out) {
API_IMPL_BEGIN
std::unique_ptr<onnxruntime::InferenceSession> sess;
OrtStatus* status = nullptr;
*out = nullptr;
ORT_TRY {
ORT_API_RETURN_IF_ERROR(CreateSessionAndLoadModel(options, env, model_path, nullptr, 0, sess));
ORT_API_RETURN_IF_ERROR(InitializeSession(options, sess));
*out = reinterpret_cast<OrtSession*>(sess.release());
}
ORT_CATCH(const std::exception& e) {
ORT_HANDLE_EXCEPTION([&]() {
status = OrtApis::CreateStatus(ORT_FAIL, e.what());
});
}
return status;
API_IMPL_END
}
到此,我们已经定位到CreateSession的具体实现内容,可以发现它主要由两个部分组成:CreateSessionAndLoadModel
和 InitializeSession
,接下来分析这两个函数。
从 CreateSessionAndLoadModel
的名字就可以看出,这个函数主要负责创建 Session,以及加载模型:
// onnxruntime/core/session/onnxruntime_c_api.cc
// provider either model_path, or modal_data + model_data_length.
// 也就是说,共有两种方式用来读取模型:一种是根据ONNX模型路径;另一种时从模型数据缓冲(Model data buffer)中读取,并且需要指定模型大小(Model data buffer size)
static ORT_STATUS_PTR CreateSessionAndLoadModel(_In_ const OrtSessionOptions* options,
_In_ const OrtEnv* env,
_In_opt_z_ const ORTCHAR_T* model_path,
_In_opt_ const void* model_data,
size_t model_data_length,
std::unique_ptr<onnxruntime::InferenceSession>& sess) {
// quick check here to decide load path. InferenceSession will provide error message for invalid values.
const Env& os_env = Env::Default(); // OS environment (注意:OS environment != ORT environment)
bool load_config_from_model =
os_env.GetEnvironmentVar(inference_session_utils::kOrtLoadConfigFromModelEnvVar) == "1";
// 创建 InferenceSession
if (load_config_from_model) {
if (model_path != nullptr) {
sess = std::make_unique<onnxruntime::InferenceSession>(
options == nullptr ? onnxruntime::SessionOptions() : options->value,
env->GetEnvironment(),
model_path);
} else {
sess = std::make_unique<onnxruntime::InferenceSession>(
options == nullptr ? onnxruntime::SessionOptions() : options->value,
env->GetEnvironment(),
model_data, static_cast<int>(model_data_length));
}
} else {
sess = std::make_unique<onnxruntime::InferenceSession>(
options == nullptr ? onnxruntime::SessionOptions() : options->value,
env->GetEnvironment());
}
// Add custom domains
if (options && !options->custom_op_domains_.empty()) {
ORT_API_RETURN_IF_STATUS_NOT_OK(sess->AddCustomOpDomains(options->custom_op_domains_));
}
// Finish load
if (load_config_from_model) {
ORT_API_RETURN_IF_STATUS_NOT_OK(sess->Load());
} else {
if (model_path != nullptr) {
ORT_API_RETURN_IF_STATUS_NOT_OK(sess->Load(model_path));
} else {
ORT_API_RETURN_IF_STATUS_NOT_OK(sess->Load(model_data, static_cast<int>(model_data_length)));
}
}
return nullptr;
}
在创建完 InferenceSession 后,需要进行初始化操作(InitializeSession
):
// onnxruntime/core/session/onnxruntime_c_api.cc
static ORT_STATUS_PTR InitializeSession(_In_ const OrtSessionOptions* options,
_In_ std::unique_ptr<::onnxruntime::InferenceSession>& sess,
_Inout_opt_ OrtPrepackedWeightsContainer* prepacked_weights_container = nullptr) {
// 创建 Providers
std::vector<std::unique_ptr<IExecutionProvider>> provider_list;
if (options) {
for (auto& factory : options->provider_factories) {
auto provider = factory->CreateProvider();
provider_list.push_back(std::move(provider));
}
}
// 注册 Providers 到 InferenceSession 中
for (auto& provider : provider_list) {
if (provider) {
ORT_API_RETURN_IF_STATUS_NOT_OK(sess->RegisterExecutionProvider(std::move(provider)));
}
}
if (prepacked_weights_container != nullptr) {
ORT_API_RETURN_IF_STATUS_NOT_OK(sess->AddPrePackedWeightsContainer(
reinterpret_cast<PrepackedWeightsContainer*>(prepacked_weights_container)));
}
// 初始化 InferenceSession
ORT_API_RETURN_IF_STATUS_NOT_OK(sess->Initialize());
return nullptr;
}
接下来,深入到 InferenceSession 的 Initialize()
函数中,这个函数水很深,需要分为几个小的模块来分析。
// onnxruntime/core/session/inference_session.cc
common::Status InferenceSession::Initialize() {
...
bool have_cpu_ep = false;
// 这里使用 {} 可以提前释放 session_mutex_,不必等到退出Initialize函数才释放,可提升效率
{
std::lock_guard<onnxruntime::OrtMutex> initial_guard(session_mutex_);
// 判断模型是否已被加载
if (!is_model_loaded_) {
LOGS(*session_logger_, ERROR) << "Model was not loaded";
return common::Status(common::ONNXRUNTIME, common::FAIL, "Model was not loaded.");
}
if (is_inited_) { // 判断是否已经初始化,如果已经初始化就可以直接退出Initialize函数了
LOGS(*session_logger_, INFO) << "Session has already been initialized.";
return common::Status::OK();
}
// 判断是否已经设置 CPU EP 来兜底,如果忘记设置,后面会自动添加
have_cpu_ep = execution_providers_.Get(onnxruntime::kCpuExecutionProvider) != nullptr;
}
if (!have_cpu_ep) {
LOGS(*session_logger_, INFO) << "Adding default CPU execution provider.";
CPUExecutionProviderInfo epi{session_options_.enable_cpu_mem_arena};
auto p_cpu_exec_provider = std::make_unique<CPUExecutionProvider>(epi);
ORT_RETURN_IF_ERROR_SESSIONID_(RegisterExecutionProvider(std::move(p_cpu_exec_provider)));
}
...
}
以上代码确保了 EPs(复数,多个EP,hhh) 已被正常设置(主要是CPU已经被用作兜底),接下来从 Ort 环境中读取共享的分配器(shared allocators),并更新 EPs:
// onnxruntime/core/session/inference_session.cc
common::Status InferenceSession::Initialize() {
...
std::string use_env_allocators = session_options_.config_options.GetConfigOrDefault(kOrtSessionOptionsConfigUseEnvAllocators,
"0");
if (use_env_allocators == "1") {
LOGS(*session_logger_, INFO) << "This session will use the allocator registered with the environment.";
UpdateProvidersWithSharedAllocators(); // 更新 EPs
}
...
接下来需要设定 SessionState,需要注意:SessionState 只能被 InferenceSession 修改,
// onnxruntime/core/session/inference_session.cc
common::Status InferenceSession::Initialize() {
...
session_state_ = std::make_unique<SessionState>(
model_->MainGraph(),
execution_providers_,
session_options_.enable_mem_pattern && session_options_.execution_mode == ExecutionMode::ORT_SEQUENTIAL,
GetIntraOpThreadPoolToUse(),
GetInterOpThreadPoolToUse(),
data_transfer_mgr_,
*session_logger_,
session_profiler_,
session_options_.use_deterministic_compute,
session_options_.enable_mem_reuse,
prepacked_weights_container_);
...
}
接下来从EPs实例中收集内核注册表(kernel registries),内核注册表分为两类:
- Custom execution provider type specific kernel registries. 》》 比如CUDA EP
- Common execution provider type specific kernel registries. 》》 比如CPU EP
这两类注册表的优先级并不相同,前者要高于后者。
// onnxruntime/core/session/inference_session.cc
common::Status InferenceSession::Initialize() {
...
ORT_RETURN_IF_ERROR_SESSIONID_(kernel_registry_manager_.RegisterKernels(execution_providers_));
...
}
在 KernelRegistryManager 中注册完注册表之后,开始执行非常重要的图优化,以及分割子图:
// onnxruntime/core/session/inference_session.cc
common::Status InferenceSession::Initialize() {
...
// add predefined transformers
// 添加预先定义的变换
ORT_RETURN_IF_ERROR_SESSIONID_(AddPredefinedTransformers(graph_transformation_mgr_,
session_options_.graph_optimization_level,
saving_runtime_optimizations));
// apply any transformations to the main graph and any subgraphs
// 在主图和子图上执行所有的优化Pass
ORT_RETURN_IF_ERROR_SESSIONID_(TransformGraph(graph, graph_transformation_mgr_,
execution_providers_, kernel_registry_manager_,
insert_cast_transformer_,
*session_state_,
saving_ort_format));
// now that all the transforms are done, call Resolve on the main graph. this will recurse into the subgraphs.
// 所有的图变换都已经执行完毕,然后开始递归分割子图
ORT_RETURN_IF_ERROR_SESSIONID_(graph.Resolve());
// Update temporary copies of metadata, input- and output definitions to the same state as the resolved graph
ORT_RETURN_IF_ERROR_SESSIONID_(SaveModelMetadata(*model_));
...
}
分割子图之后,还有一些结尾工作:
// onnxruntime/core/session/inference_session.cc
common::Status InferenceSession::Initialize() {
...
ORT_RETURN_IF_ERROR_SESSIONID_(
session_state_->FinalizeSessionState(model_location_, kernel_registry_manager_,
session_options_,
serialized_session_state,
// need to keep the initializers if saving the optimized model
!saving_model,
saving_ort_format));
// Resolve memory pattern flags of the main graph and subgraph session states
ResolveMemoryPatternFlags(*session_state_);
if (status.IsOK()) {
for (auto& xp : execution_providers_) {
auto end_status = xp->OnSessionInitializationEnd();
if (status.IsOK()) {
status = end_status;
}
}
}
...
}
让模型 Run
通过上一个阶段,已经成功构造出 NanoDet 对象,接下来需要输入图像,并由 NanoDet 来执行:
//
std::vector<types::BoxF> detected_boxes;
cv::Mat img_bgr = cv::imread(test_img_path);
nanodet->detect(img_bgr, detected_boxes);
detect 函数内部:
void NanoDet::detect(const cv::Mat &mat, std::vector<types::BoxF> &detected_boxes,
float score_threshold, float iou_threshold,
unsigned int topk, unsigned int nms_type)
{
if (mat.empty()) return;
auto img_height = static_cast<float>(mat.rows);
auto img_width = static_cast<float>(mat.cols);
const int target_height = (int) input_node_dims.at(2);
const int target_width = (int) input_node_dims.at(3);
// 0. resize & unscale
cv::Mat mat_rs;
NanoScaleParams scale_params;
this->resize_unscale(mat, mat_rs, target_height, target_width, scale_params);
// 1. make input tensor
Ort::Value input_tensor = this->transform(mat_rs);
// 2. inference scores & boxes.
auto output_tensors = ort_session->Run(
Ort::RunOptions{nullptr}, input_node_names.data(),
&input_tensor, 1, output_node_names.data(), num_outputs
);
// 3. rescale & exclude.
std::vector<types::BoxF> bbox_collection;
this->generate_bboxes(scale_params, bbox_collection, output_tensors, score_threshold, img_height, img_width);
// 4. hard|blend|offset nms with topk.
this->nms(bbox_collection, detected_boxes, iou_threshold, topk, nms_type);
}
其中,第 0 和 1 步是模型输入的预处理,这里不再深入介绍,想要了解可参考源码。接下来重点对第 2 步的 ort_seesion->Run()
进行深入剖析。
// include/onnxruntime/core/session/onnxruntime_cxx_inline.h
inline std::vector<Value> Session::Run(const RunOptions& run_options, const char* const* input_names, const Value* input_values, size_t input_count,
const char* const* output_names, size_t output_names_count) {
std::vector<Ort::Value> output_values;
for (size_t i = 0; i < output_names_count; i++)
output_values.emplace_back(nullptr);
Run(run_options, input_names, input_values, input_count, output_names, output_values.data(), output_names_count);
return output_values;
}
inline void Session::Run(const RunOptions& run_options, const char* const* input_names, const Value* input_values, size_t input_count,
const char* const* output_names, Value* output_values, size_t output_count) {
static_assert(sizeof(Value) == sizeof(OrtValue*), "Value is really just an array of OrtValue* in memory, so we can reinterpret_cast safely");
auto ort_input_values = reinterpret_cast<const OrtValue**>(const_cast<Value*>(input_values));
auto ort_output_values = reinterpret_cast<OrtValue**>(output_values);
ThrowOnError(GetApi().Run(p_, run_options, input_names, ort_input_values, input_count, output_names, output_count, ort_output_values));
}
又到了熟悉的环节,GetApi()可参考上一章节的内容,直接到 onnxruntime_c_api.cc 中查看 Run 函数对应的实现:
// onnxruntime/core/session/onnxruntime_c_api.cc
ORT_API_STATUS_IMPL(OrtApis::Run, _Inout_ OrtSession* sess, _In_opt_ const OrtRunOptions* run_options,
_In_reads_(input_len) const char* const* input_names,
_In_reads_(input_len) const OrtValue* const* input, size_t input_len,
_In_reads_(output_names_len) const char* const* output_names1, size_t output_names_len,
_Inout_updates_all_(output_names_len) OrtValue** output) {
API_IMPL_BEGIN
// 获取 inferencesession
auto session = reinterpret_cast<::onnxruntime::InferenceSession*>(sess);
const int queue_id = 0;
// 模型输入:feed_names & feeds
std::vector<std::string> feed_names(input_len);
std::vector<OrtValue> feeds(input_len);
for (size_t i = 0; i != input_len; ++i) {
if (input_names[i] == nullptr || input_names[i][0] == '\0') {
return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "input name cannot be empty");
}
feed_names[i] = input_names[i];
auto& ort_value = feeds[i] = *reinterpret_cast<const ::OrtValue*>(input[i]);
if (ort_value.Fence()) ort_value.Fence()->BeforeUsingAsInput(onnxruntime::kCpuExecutionProvider, queue_id);
}
// 模型输出:output_names & fetches
std::vector<std::string> output_names(output_names_len);
for (size_t i = 0; i != output_names_len; ++i) {
if (output_names1[i] == nullptr || output_names1[i][0] == '\0') {
return OrtApis::CreateStatus(ORT_INVALID_ARGUMENT, "output name cannot be empty");
}
output_names[i] = output_names1[i];
}
std::vector<OrtValue> fetches(output_names_len);
for (size_t i = 0; i != output_names_len; ++i) {
if (output[i] != nullptr) {
::OrtValue& value = *(output[i]);
if (value.Fence())
value.Fence()->BeforeUsingAsOutput(onnxruntime::kCpuExecutionProvider, queue_id);
fetches[i] = value;
}
}
// 调用 InferenceSession 的 Run 函数,执行推理
Status status;
if (run_options == nullptr) {
OrtRunOptions op;
status = session->Run(op, feed_names, feeds, output_names, &fetches, nullptr);
} else {
status = session->Run(*run_options, feed_names, feeds, output_names, &fetches, nullptr);
}
// Run 结束后,将 fetches 中的内容取出放到 output 中
if (!status.IsOK())
return ToOrtStatus(status);
for (size_t i = 0; i != output_names_len; ++i) {
::OrtValue& value = fetches[i];
if (value.Fence())
value.Fence()->BeforeUsingAsInput(onnxruntime::kCpuExecutionProvider, queue_id);
if (output[i] == nullptr) {
output[i] = new OrtValue(value);
}
}
return nullptr;
API_IMPL_END
}
进入到 InferenceSession::Run
的内部:
Status InferenceSession::Run(const RunOptions& run_options,
const std::vector<std::string>& feed_names, const std::vector<OrtValue>& feeds,
const std::vector<std::string>& output_names, std::vector<OrtValue>* p_fetches,
const std::vector<OrtDevice>* p_fetches_device_info) {
std::vector<IExecutionProvider*> exec_providers_to_stop;
exec_providers_to_stop.reserve(execution_providers_.NumProviders());
std::vector<AllocatorPtr> arenas_to_shrink;
// 验证输入输出,并由 FeedsFetchesManager 进行管理
ORT_RETURN_IF_ERROR_SESSIONID_(ValidateInputs(feed_names, feeds));
ORT_RETURN_IF_ERROR_SESSIONID_(ValidateOutputs(output_names, p_fetches));
FeedsFetchesInfo info(feed_names, output_names, session_state_->GetOrtValueNameIdxMap());
FeedsFetchesManager feeds_fetches_manager{std::move(info)};
// current_num_runs_ 的类型是:std::atomic<int>,表示并行运行 EP 的数量
++current_num_runs_;
// info all execution providers InferenceSession:Run started
for (auto& xp : execution_providers_) {
// call OnRunStart and add to exec_providers_to_stop if successful
auto start_func = [&xp, &exec_providers_to_stop]() {
auto status = xp->OnRunStart();
if (status.IsOK())
exec_providers_to_stop.push_back(xp.get());
return status;
};
ORT_CHECK_AND_SET_RETVAL(start_func());
}
if (run_options.only_execute_path_to_fetches) {
session_state_->UpdateToBeExecutedNodes(feeds_fetches_manager.GetFeedsFetchesInfo().fetches_mlvalue_idxs);
}
session_state_->IncrementGraphExecutionCounter();
// execute the graph
ORT_CHECK_AND_SET_RETVAL(utils::ExecuteGraph(*session_state_, feeds_fetches_manager, feeds, *p_fetches,
session_options_.execution_mode, run_options.terminate, run_logger,
run_options.only_execute_path_to_fetches));
// info all execution providers InferenceSession:Run ended
for (auto* xp : exec_providers_to_stop) {
auto status = xp->OnRunEnd(/*sync_stream*/ true);
ORT_CHECK_AND_SET_RETVAL(status);
}
--current_num_runs_;
}
至此,模型已经完成推理,接下来只需处理输出内容即可,对应 nanodet->detect() 函数的 3、4 部分。
总结
本文主要介绍了InferenceSession的构造和初始化,以及模型的推理过程,可以发现其中还是蛮复杂的。由于对ONNXRuntime的源码仍然了解有限,有许多重要的部分被略过,打算接下来分别针对突破。
【推理引擎】从源码看ONNXRuntime的执行流程的更多相关文章
- Yii2 源码分析 入口文件执行流程
Yii2 源码分析 入口文件执行流程 1. 入口文件:web/index.php,第12行.(new yii\web\Application($config)->run()) 入口文件主要做4 ...
- Mybatis 系列10-结合源码解析mybatis 的执行流程
[Mybatis 系列10-结合源码解析mybatis 执行流程] [Mybatis 系列9-强大的动态sql 语句] [Mybatis 系列8-结合源码解析select.resultMap的用法] ...
- Flask源码解析:Flask应用执行流程及原理
WSGI WSGI:全称是Web Server Gateway Interface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述服务器端如何与web应用程序通信的 ...
- asyncio源码分析之基本执行流程
基于async关键字的原生协程 # 定义一个简单的原生协程cor async def cor(): print('enter cor') print('exit cor') print(type(co ...
- 浩哥解析MyBatis源码(一)——执行流程
原创作品,可以转载,但是请标注出处地址: 一.MyBatis简介 MyBatis框架是一种轻量级的ORM框架,当下十分流行,配合Spring+Spring MVC组成SSM框架,能够胜任几乎所有的项目 ...
- 从Chrome源码看浏览器的事件机制
.aligncenter { clear: both; display: block; margin-left: auto; margin-right: auto } .crayon-line spa ...
- Alink漫谈(二) : 从源码看机器学习平台Alink设计和架构
Alink漫谈(二) : 从源码看机器学习平台Alink设计和架构 目录 Alink漫谈(二) : 从源码看机器学习平台Alink设计和架构 0x00 摘要 0x01 Alink设计原则 0x02 A ...
- 从源码看Azkaban作业流下发过程
上一篇零散地罗列了看源码时记录的一些类的信息,这篇完整介绍一个作业流在Azkaban中的执行过程,希望可以帮助刚刚接手Azkaban相关工作的开发.测试. 一.Azkaban简介 Azkaban作为开 ...
- 解密随机数生成器(二)——从java源码看线性同余算法
Random Java中的Random类生成的是伪随机数,使用的是48-bit的种子,然后调用一个linear congruential formula线性同余方程(Donald Knuth的编程艺术 ...
随机推荐
- MXNet源码分析 | Gluon接口分布式训练流程
本文主要基于MXNet1.6.0版本,对Gluon接口的分布式训练过程进行简要分析. 众所周知,KVStore负责MXNet分布式训练过程中参数的同步,那么它究竟是如何应用在训练中的呢?下面我们将从G ...
- HTML5/CSS3/JS笔记
HTML笔记: 前言: HTML无非就是围绕标签.属性.属性值这三个词展开的. (标签也可以叫做元素, 元素的内容是开始标签与结束标签之间的内容) *常规标签 <标签 属性1="属性值 ...
- MHA + Maxscale 数据库的高可用和读写分离
MySQL 常见发行版本 MySQL 标准化.自动化部署 深入浅出MySQL备份与恢复 深入理解MySQL主从复制 MySQL构架设计与容量规划 MHA Maxscale MySQL 常见发行版本 M ...
- Windows server 2016 2019远程端口修改操作
windows server 2016 2019修改远程端口操作 一.修改3389远程端口 1,按"win+r"快捷键,在对话框中输入regedit 2, 找到路径 \HKEY ...
- CobaltStrike逆向学习系列(10):TeamServer 启动流程分析
这是[信安成长计划]的第 10 篇文章 关注微信公众号[信安成长计划] 0x00 目录 0x01 基本校验与解析 0x02 初始化 0x03 启动 Listeners 在之前的分析中,都是针对 Cob ...
- C#异步编程由浅入深(三)细说Awaiter
上一篇末尾提到了Awaiter这个类型,上一篇说了,能await的对象,必须包含GetAwaiter()方法,不清楚的朋友可以看上篇文章.那么,Awaiter到底有什么特别之处呢? 首先,从上 ...
- Meterpreter后渗透阶段之远程桌面开启
实验目的 学习利用Meterpreter后渗透阶段模块来开启靶机远程桌面 实验原理 利用Meterpreter生成木马,利用木马控制靶机进行远程桌面的攻击 实验内容 利用Meterpreter后渗透阶 ...
- [编译器]dev c++单步调试
一.dev c++调试崩溃的解决方案 1.点击"工具 -> 编译选项". 2.选择"编译器"选项卡,勾选"编译时加入以下命令",输入& ...
- Java中的Unsafe在安全领域的一些应用总结和复现
目录 0 前言 1 基本使用 1.1 内存级别修改值 1.2 创建对象 1.3 创建VM Anonymous Class 2 利用姿势 2.1 修改值以关闭RASP等防御措施 2.2 创建Native ...
- NPOI导出大量数据的避免OOM解决方案【SXSSFWorkbook】
一.NPOI的基本知识 碰到了导出大量数据的需求场景:从数据读取数据大约50W,然后再前端导出给用户,整个过程希望能较快的完成.如果不能较快完成,可以给与友好的提示. 大量数据的导出耗时的主要地方: ...