You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Intel SGX中向Enclave传递vector的方法及EDL函数声明咨询

Intel SGX 传递 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:17:00