【SGX系列教程】(三)Intel-SGX 官方示例分析(SampleCode)——SampleEnclave

news2024/11/22 10:50:47

文章目录

  • 一. 引言
  • 二. README
    • 2.1 项目目的
    • 2.2 构建和执行示例代码的步骤
    • 2.3 配置参数解释
    • 2.4 配置文件分析
    • 2.5 启动令牌初始化
  • 三. 重点代码分析
    • 3.1 App文件夹
      • 3.1.1 App/App.cpp
      • 3.1.2 App/Edger8rSyntax文件夹
        • 3.1.2.1 App/Edger8rSyntax/Arrays.cpp
        • 3.1.2.2 App/Edger8rSyntax/Functions.cpp
        • 3.1.2.3 App/Edger8rSyntax/Pointers.cpp
        • 3.1.2.4 App/Edger8rSyntax/Types.cpp
      • 3.1.3 App/TrustedLibrary文件夹
        • 3.1.3.1 App/TrustedLibrary/Libc.cpp
        • 3.1.3.2 App/TrustedLibrary/Libcxx.cpp
        • 3.1.3.3 App/TrustedLibrary/Thread.cpp
    • 3.2 Enclave文件夹
      • 3.2.1 Enclave/Enclave.cpp
      • 3.2.2 Enclave/Edger8rSyntax文件夹
        • 3.2.2.1 Enclave/Edger8rSyntax/Arrays.cpp
        • 3.2.2.2 Enclave/Edger8rSyntax/Functions.cpp
        • 3.2.2.3 Enclave/Edger8rSyntax/Pointers.cpp
        • 3.2.2.4 Enclave/Edger8rSyntax/Types.cpp
      • 3.2.3 Enclave/TrustedLibrary文件夹
        • 3.2.3.1 Enclave/TrustedLibrary/Libc.cpp
        • 3.2.3.2 Enclave/TrustedLibrary/Libcxx.cpp
        • 3.2.3.3 Enclave/TrustedLibrary/Thread.cpp
  • 四. 感谢支持

一. 引言

    SampleEnclave作为enclave开发的基础示例,主要包括enclave的一些基础用法介绍,本文将结合这个示例从中学习SGX的基本使用方法。关于 SGX 开发运行环境的搭建可参考之前的一篇博客:【SGX系列教程】(一)。

二. README

------------------------
Purpose of SampleEnclave
------------------------
The project demonstrates several fundamental usages of Intel(R) Software Guard 
Extensions (Intel(R) SGX) SDK:
- Initializing and destroying an enclave
- Creating ECALLs or OCALLs
- Calling trusted libraries inside the enclave

------------------------------------
How to Build/Execute the Sample Code
------------------------------------
1. Install Intel(R) SGX SDK for Linux* OS
2. Enclave test key(two options):
    a. Install openssl first, then the project will generate a test key<Enclave_private_test.pem> automatically when you build the project.
    b. Rename your test key(3072-bit RSA private key) to <Enclave_private_test.pem> and put it under the <Enclave> folder.
3. Make sure your environment is set:
    $ source ${sgx-sdk-install-path}/environment
4. Build the project with the prepared Makefile:
    a. Hardware Mode, Debug build:
        1) Enclave with no mitigation:
            $ make
        2) Enclave with mitigations for indirects and returns only:
            $ make MITIGATION-CVE-2020-0551=CF
        3) Enclave with full mitigation:
            $ make MITIGATION-CVE-2020-0551=LOAD
    b. Hardware Mode, Pre-release build:
        1) Enclave with no mitigation:
            $ make SGX_PRERELEASE=1 SGX_DEBUG=0
        2) Enclave with mitigations for indirects and returns only:
            $ make SGX_PRERELEASE=1 SGX_DEBUG=0 MITIGATION-CVE-2020-0551=CF
        3) Enclave with full mitigation:
            $ make SGX_PRERELEASE=1 SGX_DEBUG=0 MITIGATION-CVE-2020-0551=LOAD
    c. Hardware Mode, Release build:
        1) Enclave with no mitigation:
            $ make SGX_DEBUG=0
        2) Enclave with mitigations for indirects and returns only:
            $ make SGX_DEBUG=0 MITIGATION-CVE-2020-0551=CF
        3) Enclave with full mitigation:
            $ make SGX_DEBUG=0 MITIGATION-CVE-2020-0551=LOAD
    d. Simulation Mode, Debug build:
        $ make SGX_MODE=SIM
    e. Simulation Mode, Pre-release build:
        $ make SGX_MODE=SIM SGX_PRERELEASE=1 SGX_DEBUG=0
    f. Simulation Mode, Release build:
        $ make SGX_MODE=SIM SGX_DEBUG=0
5. Execute the binary directly:
    $ ./app
6. Remember to "make clean" before switching build mode

------------------------------------------
Explanation about Configuration Parameters
------------------------------------------
TCSMaxNum, TCSNum, TCSMinPool

    These three parameters will determine whether a thread will be created
    dynamically  when there is no available thread to do the work.

StackMaxSize, StackMinSize

    For a dynamically created thread, StackMinSize is the amount of stack available
    once the thread is created and StackMaxSize is the total amount of stack that
    thread can use. The gap between StackMinSize and StackMaxSize is the stack
    dynamically expanded as necessary at runtime.

    For a static thread, only StackMaxSize is relevant which specifies the total
    amount of stack available to the thread.

HeapMaxSize, HeapInitSize, HeapMinSize

    HeapMinSize is the amount of heap available once the enclave is initialized.

    HeapMaxSize is the total amount of heap an enclave can use. The gap between
    HeapMinSize and HeapMaxSize is the heap dynamically expanded as necessary
    at runtime.

    HeapInitSize is here for compatibility.

-------------------------------------------------    
Sample configuration files for the Sample Enclave
-------------------------------------------------
With below configurations, if the signed enclave is launched on a SGX2 platform
with SGX2 supported kernel, it will be loaded with EDMM enabled. Otherwise, it
will behave in way of SGX1.

    config.01.xml: There is no dynamic thread, no dynamic heap expansion.
    config.02.xml: There is no dynamic thread. But dynamic heap expansion can happen.
    config.03.xml: There are dynamic threads. For a dynamic thread, there's no stack expansion.
    config.04.xml: There are dynamic threads. For a dynamic thread, stack will expanded as necessary.

Below configuration is only workable on a SGX2 platform with SGX2 supported kernel:

    config.05.xml: There is a user region where users could operate on.

-------------------------------------------------
Launch token initialization
-------------------------------------------------
If using libsgx-enclave-common or sgxpsw under version 2.4, an initialized variable launch_token needs to be passed as the 3rd parameter of API sgx_create_enclave. For example,

sgx_launch_token_t launch_token = {0};
sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, launch_token, NULL, &global_eid, NULL);

    根据上面的README,我们可以分析出SampleEnclave项目的目的和构建/执行步骤,有助于理解项目的实现流程。

2.1 项目目的

    SampleEnclave项目演示了Intel® SGX SDK的一些基本使用方法,其主要目的包括:

  1. 初始化销毁enclave;
  2. 创建ECALLs(Enclave Calls)OCALLs(Outside Calls);
  3. 调用enclave内的受信任库

2.2 构建和执行示例代码的步骤

  1. 安装Intel® SGX SDK for Linux OS
    这是项目构建和运行的基本前提,需要确保已正确安装SGX SDK,具体安装教程参考第一篇博客。
  2. 准备Enclave测试密钥(两种方式)
    a. 安装openssl,然后在构建项目时会自动生成一个测试密钥Enclave_private_test.pem
    b. 使用你自己的测试密钥(3072位RSA私钥),将其重命名为Enclave_private_test.pem并放入文件夹中。
  3. 设置环境变量: source ${sgx-sdk-install-path}/environment
  4. 构建项目,根据不同的模式配置进行构建,具体参考上述README。
  5. 直接执行生成的二进制文件
    ./app
  6. 切换构建模式前请记得清理构建
    make clean

2.3 配置参数解释

  1. 线程配置参数
    TCSMaxNum, TCSNum, TCSMinPool:这三个参数决定是否在没有可用线程工作时动态创建一个线程。
  2. 配置参数
    StackMaxSize, StackMinSize:对于动态创建的线程,StackMinSize 是线程创建时的栈大小,StackMaxSize 是线程可以使用的最大栈空间。对于静态线程,只需要设置StackMaxSize
  3. 配置参数
    HeapMaxSize, HeapInitSize,HeapMinSize:HeapMinSize 是enclave初始化时的堆大小,HeapMaxSize 是enclave可以使用的最大堆空间,HeapInitSize 是为了兼容而设置。

2.4 配置文件分析

    对于不同的SGX平台(SGX1和SGX2)和配置,enclave的行为会有所不同:

  1. SGX1平台的行为:
    config.01.xml:无动态线程,无动态堆扩展。
    config.02.xml:无动态线程,但动态堆扩展可以发生。
    config.03.xml:有动态线程,但没有栈扩展。
    config.04.xml:有动态线程,栈可以根据需要扩展。
  2. 仅在SGX2平台上有效的配置:
    config.05.xml:有一个用户区域,用户可以对其进行操作。

2.5 启动令牌初始化

    如果使用版本低于2.4(最新安装的都不会)的libsgx-enclave-commonsgxpsw,需要传递一个初始化的启动令牌变量作为第三个参数给API sgx_create_enclave,例如:

sgx_launch_token_t launch_token = {0};
sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, launch_token, NULL, &global_eid, NULL);

总结
    SampleEnclave项目展示了如何使用Intel® SGX SDK进行基本的enclave操作,包括初始化和销毁enclave创建ECALL和OCALL、以及调用enclave内的受信任库。通过详细的构建和执行步骤、配置参数解释及各种配置文件的分析,可以帮助用户更好地理解SGX的基本使用方法和配置技巧。

三. 重点代码分析

   文件目录如下图,App侧与Enclave侧的程序一一对应,其中App表示REE侧应用程序,Enclave表示enclave侧Ecall程序。
在这里插入图片描述

3.1 App文件夹

3.1.1 App/App.cpp

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <pwd.h>
#define MAX_PATH FILENAME_MAX

#include "sgx_urts.h"       // 包含SGX User Runtime库
#include "App.h"            // 包含应用程序自定义头文件
#include "Enclave_u.h"      // 包含Enclave生成的头文件,用于与Enclave进行交互

/* 全局EID由多个线程共享 */
sgx_enclave_id_t global_eid = 0;

/* 定义一个错误列表,用于记录sgx_create_enclave返回的错误码 */
typedef struct _sgx_errlist_t {
    sgx_status_t err;
    const char *msg; // 错误信息
    const char *sug; // 建议
} sgx_errlist_t;

/* sgx_create_enclave可能返回的错误码及其对应信息 */
static sgx_errlist_t sgx_errlist[] = {
    { SGX_ERROR_UNEXPECTED, "Unexpected error occurred.", NULL },
    { SGX_ERROR_INVALID_PARAMETER, "Invalid parameter.", NULL },
    { SGX_ERROR_OUT_OF_MEMORY, "Out of memory.", NULL },
    { SGX_ERROR_ENCLAVE_LOST, "Power transition occurred.", "Please refer to the sample \"PowerTransition\" for details." },
    { SGX_ERROR_INVALID_ENCLAVE, "Invalid enclave image.", NULL },
    { SGX_ERROR_INVALID_ENCLAVE_ID, "Invalid enclave identification.", NULL },
    { SGX_ERROR_INVALID_SIGNATURE, "Invalid enclave signature.", NULL },
    { SGX_ERROR_OUT_OF_EPC, "Out of EPC memory.", NULL },
    { SGX_ERROR_NO_DEVICE, "Invalid SGX device.", "Please make sure SGX module is enabled in the BIOS, and install SGX driver afterwards." },
    { SGX_ERROR_MEMORY_MAP_CONFLICT, "Memory map conflicted.", NULL },
    { SGX_ERROR_INVALID_METADATA, "Invalid enclave metadata.", NULL },
    { SGX_ERROR_DEVICE_BUSY, "SGX device was busy.", NULL },
    { SGX_ERROR_INVALID_VERSION, "Enclave version was invalid.", NULL },
    { SGX_ERROR_INVALID_ATTRIBUTE, "Enclave was not authorized.", NULL },
    { SGX_ERROR_ENCLAVE_FILE_ACCESS, "Can't open enclave file.", NULL },
    { SGX_ERROR_MEMORY_MAP_FAILURE, "Failed to reserve memory for the enclave.", NULL },
};

/* 检查加载enclave的错误条件 */
void print_error_message(sgx_status_t ret)
{
    size_t idx = 0;
    size_t ttl = sizeof sgx_errlist/sizeof sgx_errlist[0];
    for (idx = 0; idx < ttl; idx++) {
        if(ret == sgx_errlist[idx].err) {
            if(NULL != sgx_errlist[idx].sug)
                printf("信息: %s\n", sgx_errlist[idx].sug);
            printf("错误: %s\n", sgx_errlist[idx].msg);
            break;
        }
    }
    if (idx == ttl)
        printf("错误代码是 0x%X. 请参考 \"Intel SGX SDK Developer Reference\" 获取更多详情。\n", ret);
}

/* 初始化enclave:
 * 调用sgx_create_enclave来初始化一个enclave实例
 */
int initialize_enclave(void)
{
    sgx_status_t ret = SGX_ERROR_UNEXPECTED;
    /* 调用sgx_create_enclave来初始化一个enclave实例 */
    /* 调试支持: 将第二个参数设置为1 */
    ret = sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, NULL, NULL, &global_eid, NULL);
    if (ret != SGX_SUCCESS) {
        print_error_message(ret);
        return -1;
    }
    return 0;
}

/* OCall函数 */
void ocall_print_string(const char *str)
{
    /* 代理将会检查字符串的长度并在结尾添加空字符,防止缓冲区溢出 */
    printf("%s", str);
}

/* 应用程序入口 */
int SGX_CDECL main(int argc, char *argv[])
{
    (void)(argc);
    (void)(argv);
    /* 初始化enclave */
    if(initialize_enclave() < 0){
        printf("在退出前按下任意键 ...\n");
        getchar();
        return -1; 
    }
    /* 使用edger8r生成的属性调用 */
    edger8r_array_attributes();
    edger8r_pointer_attributes();
    edger8r_type_attributes();
    edger8r_function_attributes();
    /* 使用受信任的库函数 */
    ecall_libc_functions();
    ecall_libcxx_functions();
    ecall_thread_functions();
    /* 销毁enclave */
    sgx_destroy_enclave(global_eid);
    printf("信息: SampleEnclave成功返回。\n");
    printf("在退出前按下任意键 ...\n");
    getchar();
    return 0;
}

代码功能分析

  1. 初始化与配置
    头文件与库的引用:
    #include “sgx_urts.h”:包含SGX User Runtime库的头文件。
    #include “App.h”:包含应用程序自定义头文件。
    #include “Enclave_u.h”:包含enclave生成的头文件,用于与enclave进行交互。
    全局变量:
    sgx_enclave_id_t global_eid:用于存储enclave的全局ID,由多个线程共享。
    错误处理:
    sgx_errlist_t:定义错误列表结构,用于存储错误码、错误信息和建议。
    sgx_errlist:包含sgx_create_enclave可能返回的错误码及其对应的错误信息和建议。
    print_error_message:根据错误码打印相应的错误信息和建议。
  2. 初始化enclave
    initialize_enclave:
    调用 sgx_create_enclave 函数创建enclave实例,并将得到的enclave ID存储在 global_eid 中。
    如果创建enclave失败,则调用 print_error_message 打印错误信息,并返回-1。
  3. OCall函数
    ocall_print_string:
    将传入的字符串打印到标准输出。(在OCall过程中,代理会检查字符串长度并在字符串结尾添加空字符,以防止缓冲区溢出)。
  4. 应用程序入口
    main函数:
    初始化enclave:调用 initialize_enclave 函数初始化enclave。如果失败则打印错误信息并等待用户输入。
    调用edger8r生成的属性和函数:
    edger8r_array_attributes
    edger8r_pointer_attributes
    edger8r_type_attributes
    edger8r_function_attributes
    调用受信任的库函数:
    ecall_libc_functions
    ecall_libcxx_functions
    ecall_thread_functions
    销毁enclave:调用 sgx_destroy_enclave 函数销毁enclave实例。
    程序结束提示:显示提示信息,用户按任意键后退出程序。

总结
    通过这段代码,实现了SampleEnclave项目的核心功能,包括初始化和销毁enclave调用ECALL和OCALL、以及使用enclave内的受信任库。每一步操作都有详细的错误处理和用户提示,确保了代码的鲁棒性和易用性。

3.1.2 App/Edger8rSyntax文件夹

    Edger8r是SGX的一部分,是可信和不可信部分的边界层,用来提供一些在不可信的应用和enclave之间的一些边界路径。Edger8r在编译的时候自动执行但是一些高级的enclave开发人员会手动调用Edger8r。

3.1.2.1 App/Edger8rSyntax/Arrays.cpp
#include "../App.h"       // 包含应用程序的头文件
#include "Enclave_u.h"    // 包含Enclave生成的头文件,用于与Enclave进行交互

/* edger8r_array_attributes:
 * 调用声明了数组属性的ECALL
 */
void edger8r_array_attributes(void)
{
    sgx_status_t ret = SGX_ERROR_UNEXPECTED;

    /* user_check */
    int arr1[4] = {0, 1, 2, 3};  // 初始化数组arr1
    ret = ecall_array_user_check(global_eid, arr1); // 调用ecall_array_user_check函数
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序

    /* 确认arr1被修改 */
    for (int i = 0; i < 4; i++)
        assert(arr1[i] == (3 - i)); // 检查arr1的每个元素是否变成3-i

    /* in */
    int arr2[4] = {0, 1, 2, 3};  // 初始化数组arr2
    ret = ecall_array_in(global_eid, arr2); // 调用ecall_array_in函数
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    
    /* 确认arr2未被修改 */
    for (int i = 0; i < 4; i++)
        assert(arr2[i] == i); // 检查arr2的每个元素是否未改变
    
    /* out */
    int arr3[4] = {0, 1, 2, 3};  // 初始化数组arr3
    ret = ecall_array_out(global_eid, arr3); // 调用ecall_array_out函数
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    
    /* 确认arr3被修改 */
    for (int i = 0; i < 4; i++)
        assert(arr3[i] == (3 - i)); // 检查arr3的每个元素是否变成3-i
    
    /* in, out */
    int arr4[4] = {0, 1, 2, 3};  // 初始化数组arr4
    ret = ecall_array_in_out(global_eid, arr4); // 调用ecall_array_in_out函数
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    
    /* 确认arr4被修改 */
    for (int i = 0; i < 4; i++)
        assert(arr4[i] == (3 - i)); // 检查arr4的每个元素是否变成3-i
    
    /* isary */
    array_t arr5 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};  // 初始化数组arr5
    ret = ecall_array_isary(global_eid, arr5); // 调用ecall_array_isary函数
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    
    /* 确认arr5被修改 */
    for (int i = 0; i < 10; i++)
        assert(arr5[i] == (9 - i)); // 检查arr5的每个元素是否变成9-i
}

    该函数 edger8r_array_attributes 展示了如何通过SGX ECALL调用将数组传递给enclave进行处理,并在处理后检查数组是否按照预期进行了修改。函数中分别展示了四种不同的数组传递方式(user_check, in, out, in_out),并通过assert语句对修改后的数组进行验证。此示例演示了如何在SGX环境中进行数组相关的ECALL调用和数据验证。

3.1.2.2 App/Edger8rSyntax/Functions.cpp
#include "../App.h"       // 包含应用程序的头文件
#include "Enclave_u.h"    // 包含Enclave生成的头文件,用于与Enclave进行交互

/* edger8r_function_attributes:
 * 调用使用调用约定属性声明的ECALL。
 * 调用使用[public]声明的ECALL。
 */
void edger8r_function_attributes(void)
{
    sgx_status_t ret = SGX_ERROR_UNEXPECTED;

    // 调用公开的函数ecall_function_public
    ret = ecall_function_public(global_eid);
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    
    // 用户不应该在这里调用私有函数
    int runned = 0;
    ret = ecall_function_private(global_eid, &runned);
    // 确保返回值是SGX_ERROR_ECALL_NOT_ALLOWED,并且函数未被运行
    if (ret != SGX_ERROR_ECALL_NOT_ALLOWED || runned != 0)
        abort();  // 如果检查失败,终止程序
}

/* ocall_function_allow:
 * 该OCALL调用被[allow]属性批准的edger8r_private。
 */
void ocall_function_allow(void)
{
    int runned = 0;
    sgx_status_t ret = SGX_ERROR_UNEXPECTED;
    
    // 调用私有函数ecall_function_private
    ret = ecall_function_private(global_eid, &runned);
    // 确保返回值是SGX_SUCCESS,并且函数已被运行
    if (ret != SGX_SUCCESS || runned != 1)
        abort();  // 如果检查失败,终止程序
}

这段代码展示了如何调用SGX ECALL时处理不同的函数属性:

  • edger8r_function_attributes 函数展示了如何调用具有公开属性的ECALL函数,以及检查并防止直接调用私有的ECALL函数。
  • ocall_function_allow 函数展示了如何通过OCALL调用被允许的私有ECALL函数。
    通过这些示例,可以清楚地了解如何在SGX环境中管理和调用具有不同属性的ECALL函数,以确保代码的安全性和正确性。
3.1.2.3 App/Edger8rSyntax/Pointers.cpp
#include "../App.h"       // 包含应用程序的头文件
#include "Enclave_u.h"    // 包含Enclave生成的头文件,用于与Enclave进行交互

/* edger8r_pointer_attributes:
 * 调用声明了指针属性的ECALL。
 */
void edger8r_pointer_attributes(void)
{
    int val = 0;
    sgx_status_t ret = SGX_ERROR_UNEXPECTED;

    // 定义字符数组和长度变量
    char c[128] = {0};
    size_t len = 0;

    // 初始化字符数组c
    memset(c, 0xe, 128);
    ret = ecall_pointer_user_check(global_eid, &len, &c, 128); // 调用ecall_pointer_user_check函数
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    assert(strcmp(c, "SGX_SUCCESS") == 0); // 检查c是否被更改为"SGX_SUCCESS"

    // 测试[in]指针属性
    val = 1;
    ret = ecall_pointer_in(global_eid, &val); // 调用ecall_pointer_in函数
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    assert(val == 1); // 确保val的值未被修改

    // 测试[out]指针属性
    val = 1;
    ret = ecall_pointer_out(global_eid, &val); // 调用ecall_pointer_out函数
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    assert(val == 1234); // 确保val的值被修改为1234

    // 测试[in, out]指针属性
    val = 1;
    ret = ecall_pointer_in_out(global_eid, &val); // 调用ecall_pointer_in_out函数
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    assert(val == 1234); // 确保val的值被修改为1234

    ret = ocall_pointer_attr(global_eid); // 调用OCALL函数ocall_pointer_attr
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序

    // 测试字符串指针
    char str1[] = "1234567890";
    ret = ecall_pointer_string(global_eid, str1); // 调用ecall_pointer_string函数
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    assert(strlen(str1) == 10 && memcmp(str1, "0987654321", strlen(str1)) == 0); // 确保str1的值被正确修改

    // 测试字符串指针常量
    const char str2[] = "1234567890";
    ret = ecall_pointer_string_const(global_eid, str2); // 调用ecall_pointer_string_const函数
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    assert(strlen(str2) == 10 && memcmp(str2, "1234567890", strlen(str2)) == 0); // 确保str2未被修改

    // 测试指针大小
    char str3[] = "1234567890";
    ret = ecall_pointer_size(global_eid, (void*)str3, strlen(str3)); // 调用ecall_pointer_size函数
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    assert(strlen(str3) == 10 && memcmp(str3, "0987654321", strlen(str3)) == 0); // 确保str3的值被正确修改

    // 测试只读指针
    char str4[] = "1234567890";
    ret = ecall_pointer_isptr_readonly(global_eid, (buffer_t)str4, strlen(str4)); // 调用ecall_pointer_isptr_readonly函数
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    assert(strlen(str4) == 10 && memcmp(str4, "1234567890", strlen(str4)) == 0); // 确保str4未被修改

    // 测试带计数的指针
    int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    ret = ecall_pointer_count(global_eid, arr, 10); // 调用ecall_pointer_count函数
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    for (int i = 0; i < 10; i++)
        assert(arr[i] == (9 - i)); // 确保arr的值被正确修改为9-i

    return;
}

/* ocall_pointer_user_check:
 * 声明为[user_check]的OCALL。
 */
void ocall_pointer_user_check(int* val)
{
    (void)val;  // 安静未使用的参数警告
    assert(val != NULL);  // 确保指针val不为NULL
}

/* ocall_pointer_in:
 * 声明为[in]的OCALL。
 */
void ocall_pointer_in(int* val)
{
    *val = 1234;  // 将指针val指向的值设置为1234
}

/* ocall_pointer_out:
 * 声明为[out]的OCALL。
 */
void ocall_pointer_out(int* val)
{
    *val = 1234;  // 将指针val指向的值设置为1234
}

/* ocall_pointer_in_out:
 * 声明为[in, out]的OCALL。
 */
void ocall_pointer_in_out(int* val)
{
    *val = 1234;  // 将指针val指向的值设置为1234
}

ECALL函数总结

   在函数 edger8r_pointer_attributes中,多个不同的ECALL被调用来测试SGX环境下的指针操作。这些ECALL通过不同的指针属性(如 user_check, in, out, in, out,以及字符串指针)来演示如何在enclave内部和外部之间传递和操作数据。每次调用都使用 assert 检查数据是否已按预期进行了修改,以验证指针操作的正确性。

OCALL函数总结

  • ocall_pointer_user_check
    验证传入的指针是否为非空指针,以确保数据完整性。
  • ocall_pointer_in
    将传入的指针值设置为 1234,用于模拟出站(outbound)数据的例子。
  • ocall_pointer_out
    将传入的指针值设置为 1234,与 ocall_pointer_in 类似,但用于表示入站(inbound)数据。
  • ocall_pointer_in_out
    同时设置传入指针值为 1234,展示了入站和出站指针的双向操作。

    通过这个文件,SampleEnclave项目展示了用于实现不同指针操作的ECALL和OCALL方法。这些示例确保了在enclave内进行指针操作时的安全性和正确性,同时也增强了对指针的使用和数据传递机制的理解。

3.1.2.4 App/Edger8rSyntax/Types.cpp
#include "../App.h"       // 包含应用程序的头文件
#include "Enclave_u.h"    // 包含Enclave生成的头文件,用于与Enclave进行交互

/* edger8r_type_attributes:
 * 调用声明了基本类型的ECALL。
 */
void edger8r_type_attributes(void)
{
    sgx_status_t ret = SGX_ERROR_UNEXPECTED;

    // 调用使用char类型的ECALL
    ret = ecall_type_char(global_eid, (char)0x12); // 传递一个char类型的值0x12
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序

    // 调用使用int类型的ECALL
    ret = ecall_type_int(global_eid, (int)1234); // 传递一个int类型的值1234
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序

    // 调用使用float类型的ECALL
    ret = ecall_type_float(global_eid, (float)1234.0); // 传递一个float类型的值1234.0
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序

    // 调用使用double类型的ECALL
    ret = ecall_type_double(global_eid, (double)1234.5678); // 传递一个double类型的值1234.5678
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序

    // 调用使用size_t类型的ECALL
    ret = ecall_type_size_t(global_eid, (size_t)12345678); // 传递一个size_t类型的值12345678
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序

    // 调用使用wchar_t类型的ECALL
    ret = ecall_type_wchar_t(global_eid, (wchar_t)0x1234); // 传递一个wchar_t类型的值0x1234
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序

    // 创建并初始化一个struct_foo_t类型的结构体
    struct struct_foo_t g = {1234, 5678};
    ret = ecall_type_struct(global_eid, g); // 传递一个结构体g
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    
    // 创建并初始化一个union_union_foo_t类型的联合体
    union union_foo_t val = {0};
    ret = ecall_type_enum_union(global_eid, ENUM_FOO_0, &val); // 传递一个枚举值ENUM_FOO_0和一个联合体val
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序

    // 确认联合体val的值是否被正确修改
    assert(val.union_foo_0 == 2);
}

    这段代码展示了如何通过SGX调用包含基本类型的ECALL。函数 edger8r_type_attributes 分别演示了如何传递 charintfloatdoublesize_twchar_t 类型的数据,以及如何传递 struct union 类型的数据。通过检查每次ECALL的返回值,代码确保了ECALL调用的正确性,同时也演示了如何在SGX环境中进行各种数据类型的操作。

3.1.3 App/TrustedLibrary文件夹

3.1.3.1 App/TrustedLibrary/Libc.cpp
#include "../App.h"       // 包含应用程序的头文件
#include "Enclave_u.h"    // 包含Enclave生成的头文件,用于与Enclave进行交互

/* ecall_libc_functions:
 * 调用标准C函数。
 */
void ecall_libc_functions(void)
{
    sgx_status_t ret = SGX_ERROR_UNEXPECTED;

    // 调用ECALL来演示malloc和free的使用
    ret = ecall_malloc_free(global_eid); // 调用malloc和free的ECALL
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    
    // 初始化cpuid数组
    int cpuid[4] = {0x0, 0x0, 0x0, 0x0};
    // 调用ECALL来获取CPUID信息
    ret = ecall_sgx_cpuid(global_eid, cpuid, 0x0); // 调用获取CPUID信息的ECALL
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
}

    通过这段代码,SampleEnclave项目展示了如何在SGX enclave中调用标准C库函数。函数 ecall_libc_functions 通过两个ECALL演示了 mallocfree 的使用,以及如何获取CPUCPUID信息。每次ECALL调用后都检查其返回值以确保操作的成功。此示例有助于理解如何在SGX环境中使用标准C库函数以及执行基本的内存管理和系统操作。

3.1.3.2 App/TrustedLibrary/Libcxx.cpp
#include <stdio.h>        // 包含标准输入输出头文件

#include "../App.h"       // 包含应用程序的头文件
#include "Enclave_u.h"    // 包含Enclave生成的头文件,用于与Enclave进行交互

/* ecall_libcxx_functions:
 * 调用标准C++函数。
 */
void ecall_libcxx_functions(void)
{
    sgx_status_t ret = SGX_ERROR_UNEXPECTED;

    // 调用ECALL函数来演示C++异常处理
    ret = ecall_exception(global_eid); // 调用处理异常的ECALL
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序

    // 调用ECALL函数来演示C++标准库中的map容器
    ret = ecall_map(global_eid); // 调用处理map的ECALL
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
}

   通过这段代码,SampleEnclave项目展示了如何在SGX enclave中调用标准C++库函数。函数 ecall_libcxx_functions 通过两个ECALL函数演示了如何在enclave中处理C++异常以及使用C++标准库中的 map 容器。每次ECALL调用后都检查其返回值以确保操作的成功。此示例有助于理解如何在SGX环境中使用标准C++库函数以及执行基本的异常处理和容器操作。

3.1.3.3 App/TrustedLibrary/Thread.cpp
#include <thread>        // 包含标准C++线程库头文件
#include <stdio.h>       // 包含标准输入输出头文件
using namespace std;     // 使用标准命名空间

#include "../App.h"      // 包含应用程序的头文件
#include "Enclave_u.h"   // 包含Enclave生成的头文件,用于与Enclave进行交互

static size_t counter = 0;  // 全局计数器

/* 增加计数器的函数 */
void increase_counter(void)
{
    size_t cnr = 0;
    sgx_status_t ret = SGX_ERROR_UNEXPECTED;
    ret = ecall_increase_counter(global_eid, &cnr);  // 调用ECALL增加计数器
    if (cnr != 0) counter = cnr;  // 如果cnr不为0,更新全局计数器
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
}

/* 数据生产者函数 */
void data_producer(void)
{
    sgx_status_t ret = SGX_ERROR_UNEXPECTED;
    ret = ecall_producer(global_eid);  // 调用ECALL数据生产者
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
}

/* 数据消费者函数 */
void data_consumer(void)
{
    sgx_status_t ret = SGX_ERROR_UNEXPECTED;
    ret = ecall_consumer(global_eid);  // 调用ECALL数据消费者
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
}

/* ecall_thread_functions:
 * 调用线程函数,包括互斥锁、条件变量等。
 */
void ecall_thread_functions(void)
{
    // 创建和启动四个增加计数器的线程
    thread adder1(increase_counter);
    thread adder2(increase_counter);
    thread adder3(increase_counter);
    thread adder4(increase_counter);

    // 等待所有线程完成
    adder1.join();
    adder2.join();
    adder3.join();
    adder4.join();

    // 确认计数器的值是否符合预期
    assert(counter == 4 * LOOPS_PER_THREAD);

    printf("信息: 正在执行线程同步,请稍候...  \n");

    // 创建和启动消费和生产线程
    thread consumer1(data_consumer);
    thread producer0(data_producer);
    thread consumer2(data_consumer);
    thread consumer3(data_consumer);
    thread consumer4(data_consumer);

    // 等待所有线程完成
    consumer1.join();
    consumer2.join();
    consumer3.join();
    consumer4.join();
    producer0.join();
}

    这段代码展示了如何在SGX enclave中通过ECALL实现基本的线程操作,包括线程同步、互斥锁和条件变量等。通过创建多个线程并调用相应的ECALL函数,该示例演示了如何在enclave中进行并发操作,并验证了全局计数器的正确性。同时,该代码还演示了数据生产者和消费者的模式,以展示更复杂的线程同步机制。每个ECALL后都检查其返回值,以确保操作的成功。

3.2 Enclave文件夹

3.2.1 Enclave/Enclave.cpp

/* 
 * printf: 
 *   Invokes OCALL to display the enclave buffer to the terminal.
 */
int printf(const char* fmt, ...)
{
    char buf[BUFSIZ] = { '\0' };
    va_list ap;
    va_start(ap, fmt);
    vsnprintf(buf, BUFSIZ, fmt, ap);
    va_end(ap);
    ocall_print_string(buf);
    return (int)strnlen(buf, BUFSIZ - 1) + 1;
}

    在Enclave中的主函数Enclave.cpp主要提供printf的OCALL函数,也就是将enclave通过该函数在REE中打印出来。

3.2.2 Enclave/Edger8rSyntax文件夹

3.2.2.1 Enclave/Edger8rSyntax/Arrays.cpp
/* 测试数组属性 */
#include "sgx_trts.h"       // 包含SGX运行时库的头文件
#include "../Enclave.h"     // 包含Enclave自定义头文件
#include "Enclave_t.h"      // 包含由Edger8r工具生成的头文件,用于定义ECALL和OCALL接口
#include <string.h>         // 包含标准C库的字符串操作头文件

/* ecall_array_user_check:
 *   [user_check] 参数不会执行拷贝操作。
 */
void ecall_array_user_check(int arr[4])
{
    // 检查数组是否位于Enclave外部
    if (sgx_is_outside_enclave(arr, 4 * sizeof(int)) != 1)
        abort();
    
    // 遍历数组并修改其值
    for (int i = 0; i < 4; i++) {
        assert(arr[i] == i); // 确认数组元素的值是否为预期
        
        // 以下代码执行arr[i] = (3 - i)
        // 它将4个字节写入不受信任的内存,非8字节对齐。
        // 因此出于安全考虑需要使用memcpy_verw()
        int tmp = 3 - i;
        memcpy_verw(&arr[i], &tmp, sizeof(int));
    }
}

/* ecall_array_in:
 *   arr[] 被拷贝到受信任域中,但修改后的结果不会反映到不受信任的一侧。
 */
void ecall_array_in(int arr[4])
{
    // 遍历数组并修改其值
    for (int i = 0; i < 4; i++) {
        assert(arr[i] == i); // 确认数组元素的值是否为预期
        arr[i] = (3 - i);    // 修改数组元素的值
    }
}

/* ecall_array_out:
 *   arr[] 在Enclave内部分配,并且会被拷贝到不受信任的一侧。
 */
void ecall_array_out(int arr[4])
{
    // 遍历数组并修改其值
    for (int i = 0; i < 4; i++) {
        /* arr 不是从应用程序中拷贝过来的 */
        assert(arr[i] == 0); // 确认数组元素的初始值为0
        arr[i] = (3 - i);    // 修改数组元素的值
    }
}

/* ecall_array_in_out:
 *   arr[] 会在Enclave内部分配,其内容也会被拷贝。
 *   ECALL返回后,结果会被拷贝到外部。
 */
void ecall_array_in_out(int arr[4])
{
    // 遍历数组并修改其值
    for (int i = 0; i < 4; i++) {
        assert(arr[i] == i); // 确认数组元素的值是否为预期
        arr[i] = (3 - i);    // 修改数组元素的值
    }
}

/* ecall_array_isary:
 *   [isary] 告诉Edger8r用户定义的 'array_t' 是一个数组类型。
 */
void ecall_array_isary(array_t arr)
{
    // 检查数组是否位于Enclave外部
    if (sgx_is_outside_enclave(arr, sizeof(array_t)) != 1)
        abort();

    int n = sizeof(array_t) / sizeof(arr[0]); // 计算数组元素的个数
    // 遍历数组并修改其值
    for (int i = 0; i < n; i++) {
        assert(arr[i] == i);  // 确认数组元素的值是否为预期
        
        // 以下代码执行arr[i] = (n - 1 - i);
        // 它将4个字节写入不受信任的内存,非8字节对齐。
        // 因此出于安全考虑需要使用memcpy_verw()
        int tmp = n - 1 - i;
        memcpy_verw(&arr[i], &tmp, sizeof(int));
    }
}

    这段代码展示了多个ECALL函数,演示了如何在Enclave中处理不同的数组操作,包括传入传出双向传输数组。每个函数都通过遍历数组元素,执行各种检查和修改操作,并考虑到内存访问安全性的因素。尤其是在需要写入不受信任内存时,使用 memcpy_verw 来确保安全性。这些示例有助于理解如何在SGX环境中安全地处理数组数据。

  • Arrays.edl
/* Arrays.edl - Samples for array attributes. */
enclave {
    /* 
     *  Only for fixed-size array (size is explicitly specified).
     */
    trusted {
        /*
         * []:  can be used to declare an array.
         * [user_check]:
         *      pointer of the array won't be verified, and the buffer pointed by 'arr' 
         *      is not copied into the enclave either. But enclave can modify the memory outside.
         */
        
        public void ecall_array_user_check([user_check] int arr[4]);
        
        /*
         * [in]:
         *      buffer for the array will be allocated inside the enclave, 
         *      content of the array will be copied into the new allocated memory inside. 
         *      Any changes performed inside the enclave will not affect the array outside.
         */
        
        public void ecall_array_in([in] int arr[4]);
        
        /*
         * [out]:
         *      buffer for the array will be allocated inside the enclave,
         *      but the content of the array won't be copied. After ECALL returns, 
         *      the buffer inside the enclave will copied into outside array.
         */
        
        public void ecall_array_out([out] int arr[4]);
        
        /*
         * [in, out]:
         *      buffer for the array will be allocated inside the enclave,
         *      the content of the array will be copied either. After ECALL returns, 
         *      the buffer inside the enclave will by copied into outside array again.
         */
        
        public void ecall_array_in_out([in, out] int arr[4]);
        
        /*
         * [isary]:
         *      tells Edger8r the user defined 'array_t' is an array type, 'arr' will be 
         *      treated as a pointer, no memory copied either due to [user_check].
         *      For OCALLs, 'arr' shall point to the memory outside the enclave. 
         */
        
        public void ecall_array_isary([user_check, isary] array_t arr);
    
    };

    untrusted {
    
        /*
         * [user_check|in|out|in,out|isary] can also be used in OCALLs, refer to the "User Guide" for details.
         */

    };

};

这个 Arrays.edl 文件通过定义多个ECALL接口,详细展示了如何在SGX Enclave中处理数组数据以及不同的数组属性(如 [user_check], [in], [out], [in, out], [isary])。每种属性都影响了数组数据在Enclave中的操作和在Enclave外部的反映方式。这个文件提供了丰富的示例,有助于理解在Enclave环境中如何安全且灵活地操作数组数据。

3.2.2.2 Enclave/Edger8rSyntax/Functions.cpp
/* 测试调用约定 */
#include <string.h>       // 包含标准C库的字符串操作头文件
#include <stdio.h>        // 包含标准输入输出头文件

#include "../Enclave.h"   // 包含Enclave自定义头文件
#include "Enclave_t.h"    // 包含由Edger8r工具生成的头文件,用于定义ECALL和OCALL接口

/* ecall_function_public:
 *   一个公开的ECALL,调用OCALL 'ocall_function_allow'。
 */
void ecall_function_public(void)
{
    sgx_status_t ret = SGX_ERROR_UNEXPECTED;

    // 调用OCALL函数'ocall_function_allow'
    ret = ocall_function_allow();
    if (ret != SGX_SUCCESS)
        abort();  // 如果调用失败,终止程序
    
    return;
}

/* ecall_function_private:
 *   一个私有的ECALL,仅能在OCALL 'ocall_function_allow'中被调用。
 */
int ecall_function_private(void)
{
    return 1;  // 返回值1表示函数成功调用
}

    这段代码展示了如何在SGX Enclave中处理不同调用类型(公开 or 私有)的函数。函数 ecall_function_public 是一个公开的ECALL,它调用了一个OCALL函数 ocall_function_allow,并且检查调用是否成功;如果失败则终止程序。而 ecall_function_private是一个私有的ECALL,仅能在OCALL ocall_function_allow 中被调用,返回一个整数 1 表示成功。

    这个示例有助于理解在SGX环境中如何区分和处理公开与私有的ECALL函数,以及如何通过OCALL来调用这些ECALL函数。

  • Functions.edl
/* Functions.edl - Samples for function attributes. */
enclave {
    /* 
     * Following keywords/attributes are supported for untrusted functions: 
     *      cdecl, stdcall, fastcall, dllimport (only for Windows).
     *      [public] is only supported for the trusted functions.
     * Trusted function will be treated as [private] w/o the [public].
     */
    trusted {
        /*
         * [public]:
         *      public ECALL can be called directly in App.
         */
        
        public void ecall_function_public(void);

        /*
         * [private]:
         *      private ECALL cannot be called directly in App.
         */
        
        int ecall_function_private(void);
    
    };

    untrusted {
        /*
         * [allow]:
         *      OCALL 'ocall_function_allow' can invoke ECALL 'ecall_function_private' in App side. 
         *
         * Note: No ECALL can be called in OCALL w/o [allow].
         */
        void ocall_function_allow(void) allow(ecall_function_private);
    };
};

    这个Functions.edl文件定义了两个ECALL和一个OCALL,同时展示了如何使用函数属性来控制它们的调用权限。

  1. 公开ECALL:ecall_function_public 使用 [public] 属性,可以直接在应用程序中调用,没有限制。
  2. 私有ECALL:ecall_function_private 没有使用 [public] 属性,因此被视为私有的,不能直接在应用程序中调用。只能在被授权的OCALL中调用。
  3. OCALL与授权:ocall_function_allow 是一个OCALL,使用 [allow] 属性,授权该OCALL调用特定的私有ECALL ecall_function_private。这种机制确保了只有特定的OCALL可以调用私有的ECALL,从而增强了安全性和控制。

    这个示例有助于理解在SGX环境中如何使用EDL来定义和控制不同类型的函数调用,以及如何通过函数属性来管理这些调用的权限。

3.2.2.3 Enclave/Edger8rSyntax/Pointers.cpp
#include <string.h>
#include <sys/types.h>

#include "../Enclave.h"
#include "Enclave_t.h"
#include "sgx_lfence.h"
#include "sgx_trts.h"

/* checksum_internal:
 *   计算输入缓冲区和长度的简单校验和
 */
int32_t checksum_internal(char* buf, size_t count)
{
    register int32_t sum = 0;
    int16_t* ptr = (int16_t*)buf;

    /* 主循环求和 */
    while (count > 1) {
        sum = sum + *ptr++;
        count = count - 2;
    }

    /* 如果有剩余字节,加上它 */
    if (count > 0) {
        sum = sum + *((char*)ptr);
    }

    return ~sum;
}

/* ecall_pointer_user_check, ecall_pointer_in, ecall_pointer_out, ecall_pointer_in_out:
 *   测试[in]、[out]、[user_check]属性的根ECALL。
 */
size_t ecall_pointer_user_check(void* val, size_t sz)
{
    /* 检查缓冲区是否在enclave外分配 */
    if (sgx_is_outside_enclave(val, sz) != 1)
        abort();

    /* 在sgx_is_outside_enclave检查后插入fence */
    sgx_lfence();

    char tmp[100] = { 0 };
    size_t len = sz > 100 ? 100 : sz;

    /* 将内存复制到enclave中,以确保在checksum_internal()中不更改'val' */
    memcpy(tmp, val, len);

    int32_t sum = checksum_internal((char*)tmp, len);
    printf("Checksum(0x%p, %zu) = 0x%x\n",
        val, len, (unsigned int)sum);

    /* 直接修改外部内存 */
    memcpy_verw(val, "SGX_SUCCESS", len > 12 ? 12 : len);

    return len;
}

/* ecall_pointer_in:
 *   val的缓冲区被复制到enclave中。
 */
void ecall_pointer_in(int* val)
{
    if (sgx_is_within_enclave(val, sizeof(int)) != 1)
        abort();
    assert(*val == 1);
    *val = 1234;
}

/* ecall_pointer_out:
 *   val的缓冲区被复制到不可信的一侧。
 */
void ecall_pointer_out(int* val)
{
    if (sgx_is_within_enclave(val, sizeof(int)) != 1)
        abort();
    assert(*val == 0);
    *val = 1234;
}

/* ecall_pointer_in_out:
 *   val的缓冲区被双重复制。
 */
void ecall_pointer_in_out(int* val)
{
    if (sgx_is_within_enclave(val, sizeof(int)) != 1)
        abort();
    assert(*val == 1);
    *val = 1234;
}

/* ocall_pointer_attr:
 *   测试OCALL [in]、[out]、[user_check]的根ECALL。
 */
void ocall_pointer_attr(void)
{
    sgx_status_t ret = SGX_ERROR_UNEXPECTED;

    int val = 0;
    ret = ocall_pointer_user_check(&val);
    if (ret != SGX_SUCCESS)
        abort();

    val = 0;
    ret = ocall_pointer_in(&val);
    if (ret != SGX_SUCCESS)
        abort();
    assert(val == 0);

    val = 0;
    ret = ocall_pointer_out(&val);
    if (ret != SGX_SUCCESS)
        abort();
    assert(val == 1234);

    val = 0;
    ret = ocall_pointer_in_out(&val);
    if (ret != SGX_SUCCESS)
        abort();
    assert(val == 1234);

    return;
}

/* ecall_pointer_string:
 *   [string]定义一个字符串。
 */
void ecall_pointer_string(char* str)
{
    strncpy(str, "0987654321", strlen(str));
}

/* ecall_pointer_string_const:
 *   const [string]定义一个不能修改的字符串。
 */
void ecall_pointer_string_const(const char* str)
{
    char* temp = new char[strlen(str)];
    strncpy(temp, str, strlen(str));
    delete[] temp;
}

/* ecall_pointer_size:
 *   'len'需要指定以告诉Edger8r 'str'的长度。
 */
void ecall_pointer_size(void* ptr, size_t len)
{
    strncpy((char*)ptr, "0987654321", len);
}

/* ecall_pointer_count:
 *   'cnt'需要指定以告诉Edger8r 'arr'中的元素数量。
 */
void ecall_pointer_count(int* arr, size_t count)
{
    int cnt = (int)count;
    for (int i = (cnt - 1); i >= 0; i--)
        arr[i] = (cnt - 1 - i);
}

/* ecall_pointer_isptr_readonly:
 *   'buf'是用户定义类型,应标记为[isptr]。
 *   如果它不可写,应指定[readonly]。
 */
void ecall_pointer_isptr_readonly(buffer_t buf, size_t len)
{
    strncpy((char*)buf, "0987654321", len);
}

代码功能分析
    这段代码展示了如何在Intel SGX中处理指针属性的示例,包括[in]、[out]、[user_check]、[string]、[size]、[count]和[readonly]等属性。以下是主要功能和步骤的详细分析:

  1. 校验和计算:
    checksum_internal函数计算输入缓冲区的简单校验和。
  2. 用户检查指针:
    ecall_pointer_user_check函数检查指针是否在enclave外部分配,并计算校验和。
  3. 指针属性测试:
    ecall_pointer_in函数验证指针在enclave内并修改其值。
    ecall_pointer_out函数验证指针在enclave内并修改其值。
    ecall_pointer_in_out函数验证指针在enclave内并修改其值。
  4. OCALL指针属性测试:
    ocall_pointer_attr函数测试OCALL中的指针属性,包括用户检查、输入和输出指针。
  5. 字符串指针:
    ecall_pointer_string函数处理可修改的字符串指针。
    ecall_pointer_string_const函数处理不可修改的字符串指针。
  6. 指针大小和计数:
    ecall_pointer_size函数处理指定长度的指针。
    ecall_pointer_count函数处理指定元素数量的指针数组。
  7. 只读指针:
    ecall_pointer_isptr_readonly函数处理只读指针。
  • Pointers.edl
/* Pointers.edl - Samples for pointer attributes. */
enclave {
    /* 
     * Following keywords/attributes are supported for pointers in Edger8r: 
     *      in, out, user_check, 
     *      string, wstring,
     *      const, size, count, isptr, readonly
     */
    trusted {
        /*
         * [user_check]:
         *      the pointer won't be validated, and the buffer pointed by
         *      'val' is not copied into the enclave either. But Enclave 
         *      can modify the memory pointed by 'val'.
         */
        public size_t ecall_pointer_user_check([user_check] void *val, size_t sz);
        
        /*
         * [in]:
         *      buffer with the same size will be allocated inside the enclave,
         *      content pointed by 'val' will be copied into the new allocated
         *      memory inside. Any changes performed inside the enclave will not 
         *      affect the buffer outside.
         */
        public void ecall_pointer_in([in] int *val);
        
        /*
         * [out]:
         *      buffer with the same size will be allocated inside the enclave,
         *      but the content pointed by 'val' won't be copied. But after return, 
         *      the buffer inside the enclave will copied into outside 'val'.
         */
        public void ecall_pointer_out([out] int *val);
        
        /*
         * [in, out]:
         *      buffer with the same size will be allocated inside the enclave,
         *      the content pointed by 'val' will be copied either. After return, 
         *      the buffer inside the enclave will by copied into outside 'val' again.
         */
        public void ecall_pointer_in_out([in, out] int *val);
        
        /*
         * [string]:
         *      the attribute tells Edger8r 'str' is NULL terminated string, so strlen 
         *      will be used to count the length of buffer pointed by 'str'.
         */
        public void ecall_pointer_string([in, out, string] char *str);
        
        /*
         * [const]:
         *      the attribute tells Edger8r the buffer pointed by 'str' cannot be modified,
         *      so users cannot decorate 'str' with [out] attribute anymore.
         */
        public void ecall_pointer_string_const([in, string] const char *str);
        
        /*
         * [size]:
         *      the attribute tells Edger8r the length of buffer in byte pointed by 'ptr' 
         *      (shall be copied or not). 
         * Note: Users shall not specify [size] on [string] parameters.
         */
        public void ecall_pointer_size([in, out, size=len] void *ptr, size_t len);
        /*
         * [count]:
         *      the attribute tells Edger8r the number of integers to be copied from 'arr'.
         */
        public void ecall_pointer_count([in, out, count=cnt] int *arr, size_t cnt);
        
        /*
         * [isptr]:
         *      tells Edger8r the user defined type is a pointer; 
         * [readonly]:
         *      forbids the buffer allocated inside the enclave to be copied back to App
         *      (cannot use with [out]).
         */
        public void ecall_pointer_isptr_readonly([in, isptr, readonly, size=len] buffer_t buf, size_t len);
        
    };

    /*
     * Users can define multiple trusted/untrusted blocks, 
     * edger8r will merged them into one trusted/untrusted block.
     */
    trusted {
        /*
         * Test pointer attributes in OCALLs
         */
        public void ocall_pointer_attr(void);
    
    };

    untrusted {
    
        /*
         * [user_check]:
         *      the pointer won't be verified, and the buffer pointed by 'val' is not 
         *      copied to outside buffer either. Besides 'App' cannot modify the memory 
         *      pointer by 'val'.
         */
        
        void ocall_pointer_user_check([user_check] int *val);
        
        /*
         * [in]:
         *      buffer with the same size will be allocated in 'App' side, the content 
         *      pointed by 'val' will be copied into the new allocated memory outside. 
         *      Any changes performed by 'App' will not affect the buffer pointed by 'val'.
         */
        
        void ocall_pointer_in([in] int *val);
        
        /*
         * [out]:
         *      buffer with the same size will be allocated in 'App' side, the content
         *      pointed by 'val' won't be copied. But after return, the buffer outside
         *      will be copied into the enclave.
         */
        
        void ocall_pointer_out([out] int *val);

        /*
         * [in, out]:
         *      buffer with the same size will be allocated in 'App' side, the content
         *      pointed by 'val' will be copied either. After return, the buffer outside 
         *      will copied into the enclave.
         */
        
        void ocall_pointer_in_out([in, out] int *val);
    
    };

};
3.2.2.4 Enclave/Edger8rSyntax/Types.cpp
#include "sgx_trts.h"
#include "../Enclave.h"
#include "Enclave_t.h"
#include <limits>
#include <cmath>

/* 用于消除“未使用变量”警告 */
#define UNUSED(val) (void)(val)

#define ULP 2

/* 用于比较double变量以避免编译警告 */
bool  almost_equal(double x, double y)
{
    /* 机器epsilon必须根据较大值的量级进行缩放
       并乘以所需的ULP(最后一位的单位)精度 */
    return std::abs(x-y) <= std::numeric_limits<double>::epsilon() * std::abs(x+y) * ULP;
}

/* 用于比较float变量以避免编译警告 */
bool  almost_equal(float x, float y)
{
    /* 机器epsilon必须根据较大值的量级进行缩放
       并乘以所需的ULP(最后一位的单位)精度 */
    return std::abs(x-y) <= std::numeric_limits<float>::epsilon() * std::abs(x+y) * ULP;
}

/* ecall_type_char:
 *   [char]值由App传递。
 */
void ecall_type_char(char val)
{
    assert(val == 0x12);
#ifndef DEBUG
    UNUSED(val);
#endif
}

/* ecall_type_int:
 *   [int]值由App传递。
 */
void ecall_type_int(int val)
{
    assert(val == 1234);
#ifndef DEBUG
    UNUSED(val);
#endif
}

/* ecall_type_float:
 *   [float]值由App传递。
 */
void ecall_type_float(float val)
{
    assert(almost_equal(val, (float)1234.0));
#ifndef DEBUG
    UNUSED(val);
#endif
}

/* ecall_type_double:
 *   [double]值由App传递。
 */
void ecall_type_double(double val)
{
    assert(almost_equal(val, (double)1234.5678));
#ifndef DEBUG
    UNUSED(val);
#endif
}

/* ecall_type_size_t:
 *   [size_t]值由App传递。
 */
void ecall_type_size_t(size_t val)
{
    assert(val == (size_t)12345678);
#ifndef DEBUG
    UNUSED(val);
#endif
}

/* ecall_type_wchar_t:
 *   [wchar_t]值由App传递。
 */
void ecall_type_wchar_t(wchar_t val)
{
    assert(val == (wchar_t)0x1234);
#ifndef DEBUG
    UNUSED(val);
#endif
}

/* ecall_type_struct:
 *   struct_foo_t在EDL中定义,可以在ECALL中使用。
 */
void ecall_type_struct(struct struct_foo_t val)
{
    assert(val.struct_foo_0 == 1234);
    assert(val.struct_foo_1 == 5678);
#ifndef DEBUG
    UNUSED(val);
#endif
}

/*
 * ecall_type_enum_union:
 *   enum_foo_t/union_foo_t在EDL中定义
 *   并且可以在ECALL中使用。
 */
void ecall_type_enum_union(enum enum_foo_t val1, union union_foo_t *val2)
{
    if (sgx_is_outside_enclave(val2, sizeof(union union_foo_t)) != 1)
        abort();
    val2->union_foo_0 = 1;
    val2->union_foo_1 = 2; /* 覆盖union_foo_0 */
    assert(val1 == ENUM_FOO_0);
#ifndef DEBUG
    UNUSED(val1);
#endif
}

    这段代码展示了如何在SGX中处理各种基本类型,包括浮点数、整数、结构体、枚举和联合体。通过详细的注释和步骤分析,读者可以更好地理解SGX中基本类型的工作原理和实现细节。代码中涵盖了如何接收和验证不同类型的值,以及如何处理浮点数比较等操作。

  • Types.edl
/* Types.edl - Samples for basic types. */
enclave {
    /* 
     * Following types can be supported in Edger8r: 
     *      char, short, int, float, double, void, 
     *      int8_t, int16_t, int32_t, int64_t,
     *      size_t, wchar_t, 
     *      uint8_t, uint16_t, uint32_t, uint64_t, 
     *      unsigned, struct, enum, union.
     */

    /*
     * We will demo few types in ECALL functions, data 
     * types in OCALL functions can be handled either.
     */

     /* structure definition */
    struct struct_foo_t {
        /* Basic types can be used in structure. */
        uint32_t struct_foo_0;
        uint64_t struct_foo_1;
    };

    /* enum definition */
    enum enum_foo_t {
        ENUM_FOO_0 = 0,
        ENUM_FOO_1 = 1
    };

    /* union definition */
    union union_foo_t {
        uint32_t union_foo_0;
        uint32_t union_foo_1;
        uint64_t union_foo_3;
    };

    trusted {

        public void ecall_type_char(char val);
        public void ecall_type_int(int val);

        public void ecall_type_float(float val);
        public void ecall_type_double(double val);

        public void ecall_type_size_t(size_t val);
        public void ecall_type_wchar_t(wchar_t val);

        public void ecall_type_struct(struct struct_foo_t val);
        public void ecall_type_enum_union(enum enum_foo_t val1, [user_check] union union_foo_t *val2);
        
        /* for using user defined types, please refer to Pointers.edl, Arrays.edl. */
    };
};

3.2.3 Enclave/TrustedLibrary文件夹

3.2.3.1 Enclave/TrustedLibrary/Libc.cpp
#include <string.h>
#include "sgx_cpuid.h"
#include "sgx_trts.h"
#include "../Enclave.h"
#include "Enclave_t.h"

/* ecall_malloc_free:
 *   使用malloc/free分配/释放受信任的内存。
 */
void ecall_malloc_free(void)
{
    void *ptr = malloc(100); // 分配100字节的受信任内存
    assert(ptr != NULL); // 确保内存分配成功
    memset(ptr, 0x0, 100); // 将分配的内存设置为0
    free(ptr); // 释放内存
}

/* ecall_sgx_cpuid:
 *   使用sgx_cpuid获取CPU特性和类型。
 */
void ecall_sgx_cpuid(int cpuinfo[4], int leaf)
{
    sgx_status_t ret = sgx_cpuid(cpuinfo, leaf); // 获取指定leaf的CPU信息
    if (ret != SGX_SUCCESS) // 如果获取失败,则终止执行
        abort();
}

    这段代码展示了如何在Intel SGX中使用malloc/free分配和释放受信任的内存,以及如何使用sgx_cpuid获取CPU特性和类型。以下是主要功能和步骤的详细分析:

  • 内存分配和释放:
    ecall_malloc_free函数使用malloc分配100字节的受信任内存,并确保内存分配成功。
    使用memset将分配的内存设置为0。
    使用free释放内存。
  • 获取CPU特性和类型:
    ecall_sgx_cpuid函数使用sgx_cpuid获取指定leaf的CPU信息。
    如果获取CPU信息失败,则终止执行。
3.2.3.2 Enclave/TrustedLibrary/Libcxx.cpp
#include <cstdlib>
#include <string>

#include "../Enclave.h"
#include "Enclave_t.h"

/*
 * ecall_exception:
 *   在enclave内部抛出/捕获C++异常。
 */

void ecall_exception(void)
{
    std::string foo = "foo";
    try {
        throw std::runtime_error(foo); // 抛出runtime_error异常
    }
    catch (std::runtime_error const& e) {
        assert(foo == e.what()); // 捕获runtime_error异常,并验证异常信息
        std::runtime_error clone(""); // 创建一个空的runtime_error异常
        clone = e; // 复制异常
        assert(foo == clone.what()); // 验证复制的异常信息
    }
    catch (...) {
        assert(false); // 捕获所有其他异常,并断言失败
    }
}

#include <map>
#include <algorithm>

using namespace std;

/*
 * ecall_map:
 *   在enclave中使用STL <map>。
 */
void ecall_map(void)
{
    typedef map<char, int, less<char> > map_t; // 定义一个字符到整数的映射类型
    typedef map_t::value_type map_value; // 定义映射的值类型
    map_t m; // 创建一个映射实例

    m.insert(map_value('a', 1)); // 插入键值对('a', 1)
    m.insert(map_value('b', 2)); // 插入键值对('b', 2)
    m.insert(map_value('c', 3)); // 插入键值对('c', 3)
    m.insert(map_value('d', 4)); // 插入键值对('d', 4)

    assert(m['a'] == 1); // 验证键'a'的值为1
    assert(m['b'] == 2); // 验证键'b'的值为2
    assert(m['c'] == 3); // 验证键'c'的值为3
    assert(m['d'] == 4); // 验证键'd'的值为4

    assert(m.find('e') == m.end()); // 验证键'e'不存在于映射中
    
    return;
}

    这段代码展示了在Intel SGX中使用C++异常处理STL容器(如std::map)的示例。以下是主要功能和步骤的详细分析:

  • 异常处理:
    ecall_exception函数在enclave内部抛出和捕获C++异常
    使用try-catch块抛出std::runtime_error异常,并在catch块中捕获异常。
    验证捕获的异常信息是否与预期值一致。
    创建并复制异常对象,验证复制的异常信息是否与原始异常一致。
  • STL容器std::map的使用:
    ecall_map函数在enclave内部使用STL容器std::map
    定义一个字符到整数的映射类型,并插入一些键值对。
    验证插入的键值对是否正确。
    检查某个键是否存在于映射中。
3.2.3.3 Enclave/TrustedLibrary/Thread.cpp
#include "../Enclave.h"
#include "Enclave_t.h"

#include "sgx_thread.h"

static size_t global_counter = 0; // 全局计数器
static sgx_thread_mutex_t global_mutex = SGX_THREAD_MUTEX_INITIALIZER; // 全局互斥锁

#define BUFFER_SIZE 50 // 缓冲区大小

typedef struct {
    int buf[BUFFER_SIZE]; // 缓冲区数组
    int occupied; // 已占用的缓冲区数量
    int nextin; // 下一个写入位置
    int nextout; // 下一个读取位置
    sgx_thread_mutex_t mutex; // 互斥锁
    sgx_thread_cond_t more; // 条件变量,表示有更多数据
    sgx_thread_cond_t less; // 条件变量,表示有更少数据
} cond_buffer_t;

static cond_buffer_t buffer = {{0, 0, 0, 0, 0, 0}, 0, 0, 0,
    SGX_THREAD_MUTEX_INITIALIZER, SGX_THREAD_COND_INITIALIZER, SGX_THREAD_COND_INITIALIZER}; // 初始化缓冲区

/*
 * ecall_increase_counter:
 *   在enclave中使用线程API。
 */
size_t ecall_increase_counter(void)
{
    size_t ret = 0;
    for (int i = 0; i < LOOPS_PER_THREAD; i++) {
        sgx_thread_mutex_lock(&global_mutex); // 加锁
        /* 互斥地增加计数器 */
        size_t tmp = global_counter;
        global_counter = ++tmp;
        if (4*LOOPS_PER_THREAD == global_counter)
            ret = global_counter;
        sgx_thread_mutex_unlock(&global_mutex); // 解锁
    }
    return ret;
}

void ecall_producer(void)
{
    for (int i = 0; i < 4*LOOPS_PER_THREAD; i++) {
        cond_buffer_t *b = &buffer;
        sgx_thread_mutex_lock(&b->mutex); // 加锁
        while (b->occupied >= BUFFER_SIZE)
            sgx_thread_cond_wait(&b->less, &b->mutex); // 等待缓冲区有空闲空间
        b->buf[b->nextin] = b->nextin; // 写入数据
        b->nextin++;
        b->nextin %= BUFFER_SIZE; // 循环使用缓冲区
        b->occupied++; // 增加占用计数
        sgx_thread_cond_signal(&b->more); // 通知消费者有更多数据
        sgx_thread_mutex_unlock(&b->mutex); // 解锁
    }
}

void ecall_consumer(void)
{
    for (int i = 0; i < LOOPS_PER_THREAD; i++) {
        cond_buffer_t *b = &buffer;
        sgx_thread_mutex_lock(&b->mutex); // 加锁
        while(b->occupied <= 0)
            sgx_thread_cond_wait(&b->more, &b->mutex); // 等待缓冲区有数据
        b->buf[b->nextout++] = 0; // 读取数据
        b->nextout %= BUFFER_SIZE; // 循环使用缓冲区
        b->occupied--; // 减少占用计数
        sgx_thread_cond_signal(&b->less); // 通知生产者有空闲空间
        sgx_thread_mutex_unlock(&b->mutex); // 解锁
    }
}

    这段代码展示了如何在Intel SGX中使用线程同步原语(如互斥锁和条件变量)进行多线程编程。以下是主要功能和步骤的详细分析:

  1. 全局计数器的增加:
    ecall_increase_counter函数使用互斥锁global_mutex来保护全局计数器global_counter
    在循环中,函数锁定互斥锁,安全地增加计数器,然后解锁互斥锁。
  2. 生产者-消费者模型:
  • 使用cond_buffer_t结构体定义一个带有互斥锁条件变量的缓冲区。
  • ecall_producer函数实现生产者逻辑:
    在循环中,函数锁定缓冲区的互斥锁。
    如果缓冲区已满,等待less条件变量。
    向缓冲区写入数据,更新写入位置和占用计数。
    发出more条件变量信号,通知消费者有更多数据。
    解锁互斥锁。
  • ecall_consumer函数实现消费者逻辑:
    在循环中,函数锁定缓冲区的互斥锁。
    如果缓冲区为空,等待more条件变量。
    从缓冲区读取数据,更新读取位置和占用计数。
    发出less条件变量信号,通知生产者有空闲空间。
    解锁互斥锁。

四. 感谢支持

    完结撒花!后续将持续输出,形成Intel SGX的系列教程,并结合密码学算法设计更复杂功能。希望看到这里的小伙伴能点个关注,也欢迎志同道合的小伙伴一起广泛交流。
    码字实属不易,如果本文对你有10分帮助,就赏个10分把,感谢各位大佬支持!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1883768.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis慢查询

Redis慢查询 目录 Redis慢查询慢查询配置慢日志操作返回参数介绍 Redis的慢查询就是当命令执行时间超过预定的阈值后将这条命令记录下来&#xff0c;与MySQL的功能类似 慢查询配置 默认阈值是10毫秒&#xff0c;即10000微秒 临时修改阈值为20毫秒 127.0.0.1:6379> confi…

千元好礼等你来拿 MatrixOne最强体验官

开发者集合&#xff01;[MatrixOne最强体验官]带着丰厚的奖品走来啦&#xff01;MatrixOne 是一款高度兼容 MySQL 语法的 HTAP 数据库&#xff0c;MatrixOne Cloud (MO Cloud) 是基于 MatrixOne 内核的全托管云原生数据平台&#xff0c;具备实时 HTAP&#xff0c;多租户&#x…

VuePress介绍

从本文开始&#xff0c;动手搭建自己的博客&#xff01;希望读者能跟着一起动手&#xff0c;这样才能真正掌握。 ‍ VuePress 是什么 VuePress 是由 Vue 作者带领团队开发的&#xff0c;非常火&#xff0c;使用的人很多&#xff1b;Vue 框架官网也是用了 VuePress 搭建的。即…

hadoop集群部署【二】YARN MapReduce 的部署

提前注意&#xff1a;请注意路径是否和我的相同&#xff0c;放置的位置不同&#xff0c;请修改标红处 HDFS部署 HDFS介绍及部署http://t.csdnimg.cn/Q3H3Y 部署说明 Hadoop HDFS分布式文件系统&#xff0c;我们会启动&#xff1a; NameNode进程作为管理节点 DataNode进程…

Transformer模型原理细节解析

基本原理: Transformer 的核心概念是 自注意力机制(Self-Attention Mechanism),它允许模型在处理每个输入时“关注”输入序列的不同部分。这种机制让模型能够理解每个单词或符号与其他单词或符号之间的关系,而不是逐个地线性处理输入。 Transformer 主要由两个部分组成:…

物料编码误区解析:制造企业如何避免

物料编码是制造企业物料管理中不可或缺的一环&#xff0c;它为每种物料提供了一个唯一的标识符。在物料种类繁多的现代制造业中&#xff0c;一个科学、合理的物料编码体系对于提高物料管理效率、降低成本、防止错误和舞弊具有至关重要的作用。本文将探讨物料编码的重要性、原则…

我在中东做MCN,月赚10万美金

图片&#xff5c;Photo by Ben Koorengevel on Unsplash ©自象限原创 作者丨程心 在迪拜购物中心和世界最高建筑哈利法塔旁的主街上&#xff0c;徐晋已经“蹲”了三个小时&#xff0c;每当遇到穿着时髦的年轻男女&#xff0c;他都会上前询问&#xff0c;有没有意愿成为…

明日周刊-第14期

不好意思又拖更了哈哈哈。不过赶在7月的第一天&#xff0c;打算更新一下。建党节&#xff0c;值得纪念的一天。 文章目录 一周热点资源分享言论歌曲推荐 一周热点 国内科技新闻 深中通道建成通车 时间&#xff1a;2024年6月30日 内容&#xff1a;深圳至中山跨江通道正式建成开…

《昇思25天学习打卡营第26天 | 昇思MindSporeSSD目标检测》

26天 本节学了SSD目标检测及相关实例 SSD&#xff0c;全称Single Shot MultiBox Detector&#xff0c;是Wei Liu在ECCV 2016上提出的一种目标检测算法。 SSD目标检测主流算法分成可以两个类型&#xff1a;1.two-stage方法&#xff1a;RCNN系列&#xff1b;2.one-stage方法&am…

idea导入opencv和mediapipe

1.参考pycharm导入cv2_pycharm import cv2-CSDN博客 2.pip install opencv-python 3. python 3.8导入mediapipe 3.1 pip install mediapipe 导入报错&#xff0c; 3.2离线导入 参考Win10安装mediapipe的步骤_mediapipe安装python版本-CSDN博客 首先安装opencv-contrib-py…

IT专业入门——高考假期预习指南,我来做你的引路人

目录 认识IT知识体系 什么是计算机 按规模、速度和功能分类 按照其工作模式分类 硬件 操作系统 编程语言 对学习语言的一点建议 对于学python的一点看法 网络 数据结构与算法 数据库 Web开发 Web前端 Web后端 基础预习指南 技术路线学习一览 学习资源推荐 刷…

机器人控制系列教程之并联机器人简介

背景 根据其构件的连接是否构成闭环形式&#xff0c;机器人可分为串联机器人和并联机器人两种。对于串联机器人&#xff0c;其所有的构件以串联的结构形式连接起来&#xff0c;在空间组成一种开环结构&#xff0c;因而具有工作空间大&#xff0c;灵活性好等优点&#xff0c;但…

【AI绘画 ComfyUI】全新整合包来袭!一键安装 即开即用,超好用的工作流形式的AI绘画工具!

大家好&#xff0c;我是画画的小强 请在看这篇文章的人注意&#xff0c;本文章介绍的Comfy UI整合包是一个节点式的工作&#xff0c;流式的AI绘画界面&#xff0c;并不适合新手使用。 如果你在找的是Web UI, 请前往我之前发布一篇的文章AI绘画『Stable Diffusion』面向小白的…

每日一题-字符串相加

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std;class Solution { public:string addStrings(string num1, string num2) {//11123//从个位开…

TypeScript Project References npm 包构建小实践

npm 包输出 es/cjs 产物 在开发一个 npm 包时&#xff0c;通常需要同时输出 ES 模块和 CommonJS 模块的产物供不同的构建进行使用。在只使用tsc进行产物编译的情况下&#xff0c;我们通常可以通过配置两个独立的 tsconfig.json 配置文件&#xff0c;并在一个 npm script 中 执…

全球AI新闻速递6.27

1.北京移动&#xff1a;首个大规模训推一体智算中心投入使用。 2.亚马逊&#xff1a;研发人工智能聊天机器人“Metis”。 3.AMD Zen5 12核心锐龙AI 9 HX 370曝光。 4.联想拯救者Y9000P发布&#xff1a;最高RTX 4090、搭载小天智能体。 5.联想&#xff1a;发布moto razr 50 …

妙笔 WonderPen 专业版会员值得购买吗?

在信息爆炸的时代&#xff0c;写作已经是一项重要技能。无论是学生、职场人士还是自由职业者&#xff0c;都经常需要写点东西。 一个好的工具能让写作成为享受&#xff0c;今天就为大家推荐专业写作工具&#xff1a;妙笔。为长文写作设计&#xff0c;且有云服务功能&#xff0…

昇思25天学习打卡营第13天|BERT

一、简介&#xff1a; BERT全称是来自变换器的双向编码器表征量&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;&#xff0c;它是Google于2018年末开发并发布的一种新型语言模型。与BERT模型相似的预训练语言模型例如问答、命名实体识别、自…

fastzdp_login的第一次构建

概述 为了方便能够快捷的实现fastapi实现登录相关功能代码开发&#xff0c;决定开发一个开源的fastapi组件库&#xff0c;想了很多个名字&#xff0c;在检查pypi的时候发现都被占用了&#xff0c;所以最终决定使用fastzdp_login这个名字。 fast代表的时fastapi。zdp代表的是张…

CesiumJS【Basic】- #041 绘制纹理线(Entity方式)- 需要自定义着色器

文章目录 绘制纹理线(Entity方式)- 需要自定义着色器1 目标2 代码2.1 main.ts3 资源文件绘制纹理线(Entity方式)- 需要自定义着色器 1 目标 使用Entity方式绘制纹理线 2 代码 2.1 main.ts import * as Cesium from cesium;const viewer = new Cesium.Viewer