Intel SGX中向Enclave传递vector的方法及EDL函数声明咨询
vector<string>/vector<vector<string>> 到Enclave的实现与问题排查 我来帮你搞定SGX里传递STL容器的问题,还有排查你遇到的「编译通过但Enclave没收到数据」的坑。先从基础的vector<string>讲起,搞懂这个之后,二维的容器就是依葫芦画瓢了。
一、EDL 文件声明
SGX的ECall没法直接传STL容器,得把容器拆成原始指针和长度这类SGX能处理的类型。先看传递vector<string>的EDL示例:
1. 传递 vector<string> 的EDL示例
enclave { trusted { // ECall:接收字符串列表,参数分别是字符串数组、每个字符串的长度、列表总大小 void ecall_receive_string_list([in] char** str_array, [in] size_t* str_lengths, [in] size_t list_size); }; untrusted { // OCall:给Enclave用的打印函数,方便验证数据是否收到 void ocall_print_string([in] const char* str); }; }
要是传二维的vector<vector<string>>,EDL就得再加一层参数,比如二级指针、子列表大小数组这些,后面会给示例。
二、App端(不可信区)代码实现
App端要把vector<string>转换成EDL里定义的参数格式,得手动分配内存、拷贝字符串,调用ECall之后再释放内存(因为SGX会把数据拷贝到Enclave的可信内存里,App端的内存可以放心释放)。
1. 传递 vector<string> 的App示例
#include "sgx_urts.h" #include "enclave_u.h" // 这是EDL自动生成的头文件 #include <vector> #include <string> #include <cstdio> #include <cstdlib> #include <cstring> void send_string_list_to_enclave(sgx_enclave_id_t eid, const std::vector<std::string>& str_list) { // 1. 分配内存,准备传递的参数 char** str_array = (char**)malloc(str_list.size() * sizeof(char*)); size_t* str_lengths = (size_t*)malloc(str_list.size() * sizeof(size_t)); for (size_t i = 0; i < str_list.size(); ++i) { const std::string& s = str_list[i]; str_lengths[i] = s.size(); // 要给字符串终止符留位置,所以分配size+1的内存 str_array[i] = (char*)malloc(s.size() + 1); memcpy(str_array[i], s.c_str(), s.size() + 1); } // 2. 调用ECall,把数据传给Enclave sgx_status_t status = ecall_receive_string_list(eid, str_array, str_lengths, str_list.size()); if (status != SGX_SUCCESS) { printf("ECall调用失败,状态码:0x%x\n", status); } // 3. 释放App端分配的内存,避免泄漏 for (size_t i = 0; i < str_list.size(); ++i) { free(str_array[i]); } free(str_array); free(str_lengths); } int main() { sgx_enclave_id_t eid; // 创建Enclave,这里要替换成你自己的Enclave签名文件名 sgx_status_t ret = sgx_create_enclave("enclave.signed.so", SGX_DEBUG_FLAG, NULL, NULL, &eid, NULL); if (ret != SGX_SUCCESS) { printf("创建Enclave失败,状态码:0x%x\n", ret); return -1; } // 测试用的字符串列表 std::vector<std::string> test_list = {"Hello SGX!", "这是一个测试", "传递Vector字符串"}; send_string_list_to_enclave(eid, test_list); sgx_destroy_enclave(eid); return 0; }
三、Enclave端(可信区)代码实现
Enclave端要把传递过来的指针和长度重新拼成vector<string>,然后就可以正常处理了。如果要验证数据,就调用OCall打印出来。
1. 接收 vector<string> 的Enclave示例
#include "sgx_trts.h" #include "enclave_t.h" // EDL自动生成的可信区头文件 #include <vector> #include <string> #include <cstdio> #include <cstdlib> #include <cstring> void ecall_receive_string_list(char** str_array, size_t* str_lengths, size_t list_size) { std::vector<std::string> received_list; received_list.reserve(list_size); // 提前分配内存,提升效率 for (size_t i = 0; i < list_size; ++i) { // 用传递过来的字符串指针和长度构建string对象 std::string s(str_array[i], str_lengths[i]); received_list.push_back(s); // 调用OCall打印,验证是否收到数据 char* print_buf = (char*)malloc(s.size() + 1); memcpy(print_buf, s.c_str(), s.size() + 1); ocall_print_string(print_buf); free(print_buf); } // 打印总数量,确认整体接收情况 char size_buf[64]; snprintf(size_buf, sizeof(size_buf), "总共收到 %zu 个字符串", list_size); ocall_print_string(size_buf); } // OCall的声明,具体实现在App端 void ocall_print_string(const char* str) { // 这里不需要写实现,App端会提供 }
四、扩展到 vector<vector<string>> 的实现
传递二维容器就是多了一层循环,参数也多了一级,逻辑和一维的完全一致:
1. EDL声明
enclave { trusted { // ECall:接收二维字符串列表 void ecall_receive_2d_string_list([in] char*** str_2d_array, [in] size_t** str_lengths_2d, [in] size_t* sub_list_sizes, [in] size_t outer_size); }; untrusted { void ocall_print_string([in] const char* str); }; }
2. App端代码
void send_2d_string_list_to_enclave(sgx_enclave_id_t eid, const std::vector<std::vector<std::string>>& str_2d_list) { size_t outer_size = str_2d_list.size(); char*** str_2d_array = (char***)malloc(outer_size * sizeof(char**)); size_t** str_lengths_2d = (size_t**)malloc(outer_size * sizeof(size_t*)); size_t* sub_list_sizes = (size_t*)malloc(outer_size * sizeof(size_t)); for (size_t i = 0; i < outer_size; ++i) { const auto& sub_list = str_2d_list[i]; sub_list_sizes[i] = sub_list.size(); str_2d_array[i] = (char**)malloc(sub_list.size() * sizeof(char*)); str_lengths_2d[i] = (size_t*)malloc(sub_list.size() * sizeof(size_t)); for (size_t j = 0; j < sub_list.size(); ++j) { const std::string& s = sub_list[j]; str_lengths_2d[i][j] = s.size(); str_2d_array[i][j] = (char*)malloc(s.size() + 1); memcpy(str_2d_array[i][j], s.c_str(), s.size() + 1); } } sgx_status_t status = ecall_receive_2d_string_list(eid, str_2d_array, str_lengths_2d, sub_list_sizes, outer_size); if (status != SGX_SUCCESS) { printf("二维列表ECall调用失败,状态码:0x%x\n", status); } // 逐层释放内存 for (size_t i = 0; i < outer_size; ++i) { for (size_t j = 0; j < sub_list_sizes[i]; ++j) { free(str_2d_array[i][j]); } free(str_2d_array[i]); free(str_lengths_2d[i]); } free(str_2d_array); free(str_lengths_2d); free(sub_list_sizes); }
3. Enclave端代码
void ecall_receive_2d_string_list(char*** str_2d_array, size_t** str_lengths_2d, size_t* sub_list_sizes, size_t outer_size) { std::vector<std::vector<std::string>> received_2d_list; received_2d_list.reserve(outer_size); char outer_buf[64]; snprintf(outer_buf, sizeof(outer_buf), "外层列表大小:%zu", outer_size); ocall_print_string(outer_buf); for (size_t i = 0; i < outer_size; ++i) { size_t sub_size = sub_list_sizes[i]; std::vector<std::string> sub_list; sub_list.reserve(sub_size); char sub_buf[64]; snprintf(sub_buf, sizeof(sub_buf), "第 %zu 个子列表大小:%zu", i, sub_size); ocall_print_string(sub_buf); for (size_t j = 0; j < sub_size; ++j) { std::string s(str_2d_array[i][j], str_lengths_2d[i][j]); sub_list.push_back(s); char* print_buf = (char*)malloc(s.size() + 1); memcpy(print_buf, s.c_str(), s.size() + 1); ocall_print_string(print_buf); free(print_buf); } received_2d_list.push_back(sub_list); } }
五、排查你遇到的「编译通过但Enclave未收到数据」问题
编译过但没数据,大概率是细节没处理好,你可以按以下步骤逐一排查:
1. 检查EDL的参数属性
确保所有传递给Enclave的指针参数都加了[in]标记!如果没加,SGX不会把App端的内存拷贝到可信区,Enclave访问的是不可信内存,要么读到垃圾数据,要么直接没数据。
2. 验证App端的数据拷贝是否正确
- 有没有给字符串分配足够的内存?比如是不是忘记加
+1来存\0终止符? - 检查memcpy的长度是不是
size()+1,如果只拷贝了size长度,字符串会因为没有终止符而乱码。 - 确认传递的大小参数(比如list_size)是正确的,有没有传成0或者错误的数值?
3. 检查Enclave端的处理逻辑
- 有没有用对传递过来的长度值?比如别用
strlen来获取字符串长度,要是字符串里包含\0,strlen会提前截断,导致数据丢失。 - 遍历数组的时候有没有用对大小参数?比如别写死循环次数,要用传递过来的list_size或者sub_size。
4. 确认OCall的实现是否正确
你的打印OCall是不是在App端正确实现了?比如:
// App端的OCall实现,必须写对! void ocall_print_string(const char* str) { if (str != NULL) { printf("%s\n", str); } }
如果OCall实现有问题(比如没判空、printf参数错误),就算Enclave收到了数据,你也看不到输出,误以为没收到。
5. 检查Enclave是否成功创建
App端调用sgx_create_enclave之后,一定要检查返回值是不是SGX_SUCCESS!如果Enclave创建失败,后续的ECall根本不会执行,自然看不到任何数据。
6. 避免传递栈内存给ECall
App端不能把栈上的局部数组直接传给ECall!必须用malloc分配堆内存,因为栈内存属于不可信区,Enclave没法直接访问,必须通过[in]属性让SGX拷贝堆内存的数据到可信区。
内容的提问来源于stack exchange,提问作者Kumar Roshan Mehta

