From 06c539e18a3a6ba1f25924600d6ff733e09cd1be Mon Sep 17 00:00:00 2001 From: leejiawei Date: Mon, 23 May 2022 20:55:18 +0800 Subject: [PATCH] add libpurgeablemem to support purgeable mem Signed-off-by: Chengke Wang Signed-off-by: lijiawei --- bundle.json | 8 +- libpurgeablemem/BUILD.gn | 32 ++ libpurgeablemem/include/pm_builder.h | 52 +++ libpurgeablemem/include/pm_ptr_util.h | 36 ++ libpurgeablemem/include/pm_state.h | 80 ++++ libpurgeablemem/include/purgeable_mem.h | 128 +++++++ libpurgeablemem/include/ux_page_table.h | 70 ++++ libpurgeablemem/src/pm_builder.c | 118 ++++++ libpurgeablemem/src/purgeable_mem.c | 427 ++++++++++++++++++++++ libpurgeablemem/src/ux_page_table.c | 317 ++++++++++++++++ libpurgeablemem/test/BUILD.gn | 45 +++ libpurgeablemem/test/purgeable_c_test.cpp | 297 +++++++++++++++ 12 files changed, 1607 insertions(+), 3 deletions(-) create mode 100644 libpurgeablemem/BUILD.gn create mode 100644 libpurgeablemem/include/pm_builder.h create mode 100644 libpurgeablemem/include/pm_ptr_util.h create mode 100644 libpurgeablemem/include/pm_state.h create mode 100644 libpurgeablemem/include/purgeable_mem.h create mode 100644 libpurgeablemem/include/ux_page_table.h create mode 100644 libpurgeablemem/src/pm_builder.c create mode 100644 libpurgeablemem/src/purgeable_mem.c create mode 100644 libpurgeablemem/src/ux_page_table.c create mode 100644 libpurgeablemem/test/BUILD.gn create mode 100644 libpurgeablemem/test/purgeable_c_test.cpp diff --git a/bundle.json b/bundle.json index 399b504..e3ca11e 100644 --- a/bundle.json +++ b/bundle.json @@ -20,12 +20,14 @@ }, "build": { "sub_component": [ - "//utils/memory/libdmabufheap:libdmabufheap" + "//utils/memory/libdmabufheap:libdmabufheap", + "//utils/memory/libpurgeablemem:libpurgeablemem" ], "inner_kits": [], "test": [ - "//utils/memory/libdmabufheap/test:unittest" + "//utils/memory/libdmabufheap/test:unittest", + "//utils/memory/libpurgeablemem/test:libpurgeablemem_test" ] } } -} \ No newline at end of file +} diff --git a/libpurgeablemem/BUILD.gn b/libpurgeablemem/BUILD.gn new file mode 100644 index 0000000..c8ec138 --- /dev/null +++ b/libpurgeablemem/BUILD.gn @@ -0,0 +1,32 @@ +# Copyright (c) 2022 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") + +config("libpurgeable_config") { + include_dirs = [ "include" ] +} + +ohos_shared_library("libpurgeablemem") { + sources = [ + "src/pm_builder.c", + "src/purgeable_mem.c", + "src/ux_page_table.c", + ] + include_dirs = [ "include" ] + deps = [ "//utils/native/base:utils" ] + external_deps = [ "hiviewdfx_hilog_native:libhilog" ] + public_configs = [ ":libpurgeable_config" ] + subsystem_name = "utils" + part_name = "utils_memory" +} diff --git a/libpurgeablemem/include/pm_builder.h b/libpurgeablemem/include/pm_builder.h new file mode 100644 index 0000000..68804f0 --- /dev/null +++ b/libpurgeablemem/include/pm_builder.h @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIB_PURGEABLE_MEM_PM_BUILDER_H +#define LIB_PURGEABLE_MEM_PM_BUILDER_H + +#include + +#ifdef __cplusplus +#if __cplusplus +extern "C" { +#endif /* End of #if __cplusplus */ +#endif /* End of #ifdef __cplusplus */ + +/* purgeable mem builder */ +struct PurgMemBuilder; + +typedef bool (*PurgMemBuilderFunc)(void *, size_t, void *); + + +struct PurgMemBuilder *PurgMemBuilderCreate(PurgMemBuilderFunc func, void *param, const char *name); + +/* If return true, @builder will be set to NULL to avoid Use-After-Free */ +bool PurgMemBuilderDestroy(struct PurgMemBuilder *builder); + +bool PurgMemBuilderAppendFunc(struct PurgMemBuilder *builder, PurgMemBuilderFunc func, void *param, + const char *name); + +bool PurgMemBuilderAppendBuilder(struct PurgMemBuilder *builder, struct PurgMemBuilder *newcomer); + +/* build @data content from @builder */ +bool PurgMemBuilderBuildAll(struct PurgMemBuilder *builder, void *data, size_t size); + +#ifdef __cplusplus +#if __cplusplus +} +#endif /* End of #if __cplusplus */ +#endif /* End of #ifdef __cplusplus */ + +#endif /* LIB_PURGEABLE_MEM_PM_BUILDER_H */ diff --git a/libpurgeablemem/include/pm_ptr_util.h b/libpurgeablemem/include/pm_ptr_util.h new file mode 100644 index 0000000..e0263d5 --- /dev/null +++ b/libpurgeablemem/include/pm_ptr_util.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIB_PURGEABLE_MEM_PM_PTR_UTIL_H +#define LIB_PURGEABLE_MEM_PM_PTR_UTIL_H + +#include "hilog/log.h" + +#define IF_NULL_LOG_ACTION(pointer, log, action) \ + do { \ + if (!(pointer)) { \ + HILOG_ERROR(LOG_CORE, "%{public}s:", log); \ + action; \ + } \ + } while (0) + +#define IF_NULL_LOG(pointer, log) \ + do { \ + if (!(pointer)) { \ + HILOG_ERROR(LOG_CORE, "%{public}s:", log); \ + } \ + } while (0) + +#endif /* LIB_PURGEABLE_MEM_PM_PTR_UTIL_H */ diff --git a/libpurgeablemem/include/pm_state.h b/libpurgeablemem/include/pm_state.h new file mode 100644 index 0000000..fd0bbce --- /dev/null +++ b/libpurgeablemem/include/pm_state.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIB_PURGEABLE_MEM_PM_STATE_H +#define LIB_PURGEABLE_MEM_PM_STATE_H + +#ifdef __cplusplus +#if __cplusplus +extern "C" { +#endif /* End of #if __cplusplus */ +#endif /* End of #ifdef __cplusplus */ + +typedef enum { + PM_OK = 0, + PM_BUILDER_NULL, + PM_MMAP_PURG_FAIL, + PM_MMAP_UXPT_FAIL, + PM_UNMAP_PURG_SUCC, + PM_UNMAP_PURG_FAIL, + PM_UNMAP_UXPT_FAIL, + PM_UXPT_OUT_RANGE, + PM_UXPT_PRESENT_DATA_PURGED, + PM_UXPT_NO_PRESENT, + PM_LOCK_INIT_FAIL, + PM_LOCK_READ_SUCC, + PM_LOCK_READ_FAIL, + PM_LOCK_WRITE_FAIL, + PM_UNLOCK_READ_FAIL, + PM_UNLOCK_WRITE_FAIL, + PM_DATA_PURGED, + PM_DATA_NO_PURGED, + PMB_BUILD_ALL_SUCC, + PMB_BUILD_ALL_FAIL, + PMB_DESTORY_FAIL, /* builder destory failed */ + PM_ERR_TYPES, +} PMState; /* purgeable mem errno */ + +static const char * const PMStateNames[PM_ERR_TYPES] = { + "OK", + "BuilderPtrNULL", + "MmapPurgMemFail", + "MmapUxptFail", + "UnmapPurgMemSucc", + "UnmapPurgMemFail", + "UnmapUxptFail", + "UxpteOutOfRange", + "UxptePresentDataPurged", + "UxpteNoPresent", + "LockInitFail", + "ReadLockSucc", + "ReadLockFail", + "WriteLockFail" + "ReadUnlockFail", + "WriteUnlockFail", + "DataPurged", + "DataNoPurged", + "BuilderBuildAllSucc", + "BuilderBuildAllFail", + "BuilderDestoryFailed", +}; + +#ifdef __cplusplus +#if __cplusplus +} +#endif /* End of #if __cplusplus */ +#endif /* End of #ifdef __cplusplus */ + +#endif /* LIB_PURGEABLE_MEM_PM_STATE_H */ diff --git a/libpurgeablemem/include/purgeable_mem.h b/libpurgeablemem/include/purgeable_mem.h new file mode 100644 index 0000000..517c751 --- /dev/null +++ b/libpurgeablemem/include/purgeable_mem.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIB_PURGEABLE_MEM_PURGEABLE_MEM_H +#define LIB_PURGEABLE_MEM_PURGEABLE_MEM_H + +#include + +#ifdef __cplusplus +#if __cplusplus +extern "C" { +#endif /* End of #if __cplusplus */ +#endif /* End of #ifdef __cplusplus */ + +/* Purgeable mem struct */ +struct PurgMem; + +/* + * Function pointer, it points to a function which build content of a PurgMem obj. + * Input: void *: data ptr, ponits to start address of a PurgMem obj's content. + * Input: size_t: data size of the content. + * Input: void *: other private parameters. + * Return: build content result, true means success, while false is fail. + */ +typedef bool (*PurgMemModifyFunc)(void *, size_t, void *); + +/* + * PurgMemCreate: create a PurgMem obj. + * Input: @size: data size of a PurgMem obj's content. + * Input: @func: function pointer, it recover data when the PurgMem obj's content is purged. + * Input: @funcPara: parameters used by @func. + * Return: a PurgMem obj. + */ +struct PurgMem *PurgMemCreate(size_t size, PurgMemModifyFunc func, void *funcPara); + +/* + * PurgMemDestroy: destroy a PurgMem obj. + * Input: @purgObj: a PurgMem obj to be destroyed. + * Return: true is success, while false is fail. return true if @purgObj is NULL. + * If return true, @purgObj will be set to NULL to avoid Use-After-Free. + */ +bool PurgMemDestroy(struct PurgMem *purgObj); + +/* + * PurgMemBeginRead: begin read a PurgMem obj. + * Input: @purgObj: a PurgMem obj. + * Return: return true if @purgObj's content is present. + * if content is purged, system will recover its data, + * return false if content is purged and recover failed. + * OS cannot reclaim the memory of @purgObj's content + * when this function return true until PurgMemEndRead() is called. + */ +bool PurgMemBeginRead(struct PurgMem *purgObj); + +/* + * PurgMemEndRead: end read a PurgMem obj. + * Input: @purgObj: a PurgMem obj. + * OS may reclaim the memory of @purgObj's content + * at a later time when this function returns. + */ +void PurgMemEndRead(struct PurgMem *purgObj); + +/* + * PurgMemBeginWrite: begin read a PurgMem obj. + * Input: @purgObj: a PurgMem obj. + * Return: return true if @purgObj's content is present. + * if content is purged, system will recover its data, + * return false if content is purged and recover failed. + * OS cannot reclaim the memory of @purgObj's content + * when this function return true until PurgMemEndWrite() is called. + */ +bool PurgMemBeginWrite(struct PurgMem *purgObj); + +/* + * PurgMemEndWrite: end write a PurgMem obj. + * Input: @purgObj: a PurgMem obj. + * OS may reclaim the memory of @purgObj's content + * at a later time when this function returns. + */ +void PurgMemEndWrite(struct PurgMem *purgObj); + +/* + * PurgMemGetContent: get content ptr of a PurgMem obj. + * Input: @purgObj: a PurgMem obj. + * Return: return start address of a PurgMem obj's content. + * Return NULL if @purgObj is NULL. + * This function should be protect by PurgMemEndRead()/PurgMemEndRead() + * or PurgMemBeginWrite()/PurgMemEndWrite() + */ +void *PurgMemGetContent(struct PurgMem *purgObj); + +/* + * PurgMemGetContentSize: get content size of a PurgMem obj. + * Input: @purgObj: a PurgMem obj. + * Return: return content size of @purgObj. + * Return 0 if @purgObj is NULL. + */ +size_t PurgMemGetContentSize(struct PurgMem *purgObj); + +/* + * PurgMemAppendModify: append a modify to a PurgMem obj. + * Input: @purgObj: a PurgMem obj. + * Input: @size: data size of a PurgMem obj's content. + * Input: @func: function pointer, it modifies content of @PurgMem. + * Input: @funcPara: parameters used by @func. + * Return: append result, true is success, while false is fail. + */ +bool PurgMemAppendModify(struct PurgMem *purgObj, PurgMemModifyFunc func, void *funcPara); + +#ifdef __cplusplus +#if __cplusplus +} +#endif /* End of #if __cplusplus */ +#endif /* End of #ifdef __cplusplus */ + +#endif /* LIB_PURGEABLE_MEM_PURGEABLE_MEM_H */ diff --git a/libpurgeablemem/include/ux_page_table.h b/libpurgeablemem/include/ux_page_table.h new file mode 100644 index 0000000..d4e8873 --- /dev/null +++ b/libpurgeablemem/include/ux_page_table.h @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef LIB_PURGEABLE_MEM_UX_PAGE_TABLE_H +#define LIB_PURGEABLE_MEM_UX_PAGE_TABLE_H + +#include /* uint64_t */ +#include /* bool */ +#include /* size_t */ +#include "pm_state.h" + +#ifdef __cplusplus +#if __cplusplus +extern "C" { +#endif /* End of #if __cplusplus */ +#endif /* End of #ifdef __cplusplus */ + +/* + * USE_UXPT is true means using uxpt using uxpt in libpurgeable, + * while false means not using uxpt, false will be used in the following cases: + * case 1: if there is no purgeable mem module in kernel. + * case 2: if you want close libpurgeable, meanwhile doesn't affect user programs. + */ +#define USE_UXPT false + +#if defined(USE_UXPT) && (USE_UXPT == true) +#define MAP_USEREXPTE 0x80 +#else +#define MAP_USEREXPTE 0x0 +#endif + +/* + * using uint64_t as uxpte_t to avoid avoid confusion on 32-bit and 64 bit systems. + * Type uxpte_t may be modified to uint32_t in the future, so typedef is used. + */ +typedef uint64_t uxpte_t; + +/* user extend page table */ +struct UxPageTable; + +bool UxpteIsEnabled(void); +size_t UxPageTableSize(void); + +PMState InitUxPageTable(struct UxPageTable *upt, void *addr, size_t len); +PMState DeinitUxPageTable(struct UxPageTable *upt); + +void UxpteGet(struct UxPageTable *upt, void *addr, size_t len); +void UxptePut(struct UxPageTable *upt, void *addr, size_t len); +bool UxpteIsPresent(struct UxPageTable *upt, void *addr, size_t len); + + +#ifdef __cplusplus +#if __cplusplus +} +#endif /* End of #if __cplusplus */ +#endif /* End of #ifdef __cplusplus */ + +#endif /* LIB_PURGEABLE_MEM_UX_PAGE_TABLE_H */ diff --git a/libpurgeablemem/src/pm_builder.c b/libpurgeablemem/src/pm_builder.c new file mode 100644 index 0000000..88e81cd --- /dev/null +++ b/libpurgeablemem/src/pm_builder.c @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include /* bool */ +#include /* NULL */ +#include /* malloc */ + +#include "hilog/log.h" +#include "pm_ptr_util.h" +#include "pm_builder.h" + +#undef LOG_TAG +#define LOG_TAG "libpurgeablemem: builder" + +/* purgeable mem builder */ +struct PurgMemBuilder { + struct PurgMemBuilder *nextBuilder; + PurgMemBuilderFunc Build; + void *param; + const char *name; +}; + +/* append a guest builder @newcomer to @head */ +static void AppendBuilder_(struct PurgMemBuilder *head, struct PurgMemBuilder *newcomer); + +struct PurgMemBuilder *PurgMemBuilderCreate(PurgMemBuilderFunc func, void *param, const char *name) +{ + IF_NULL_LOG_ACTION(func, "func is NULL", return NULL); + + struct PurgMemBuilder *builder = NULL; + builder = (struct PurgMemBuilder *)malloc(sizeof(struct PurgMemBuilder)); + if (!builder) { + HILOG_ERROR(LOG_CORE, "%{public}s: malloc struct PurgMemBuilder failed", __func__); + return NULL; + } + builder->Build = func; + builder->nextBuilder = NULL; + builder->param = param; + builder->name = name; + return builder; +} + +bool PurgMemBuilderDestroy(struct PurgMemBuilder *builder) +{ + IF_NULL_LOG_ACTION(builder, "builder is NULL", return true); + + struct PurgMemBuilder *curr = builder; + struct PurgMemBuilder *next = NULL; + while (curr) { + next = curr->nextBuilder; + free(curr); + curr = next; + } + /* set input para NULL to avoid UAF */ + builder = NULL; + return true; +} + +bool PurgMemBuilderAppendFunc(struct PurgMemBuilder *builder, PurgMemBuilderFunc func, void *param, + const char *name) +{ + IF_NULL_LOG_ACTION(builder, "input builder is NULL", return false); + IF_NULL_LOG_ACTION(func, "input func is NULL", return false); + + struct PurgMemBuilder *newcomer = PurgMemBuilderCreate(func, param, name); + IF_NULL_LOG_ACTION(newcomer, "create new builder failed", return false); + + AppendBuilder_(builder, newcomer); + return true; +} + +/* build @data content from @builder */ +bool PurgMemBuilderBuildAll(struct PurgMemBuilder *builder, void *data, size_t size) +{ + if (!builder->Build) { + HILOG_ERROR(LOG_CORE, "builder has no Build(), %{public}s", builder->name); + return true; + } + if (!builder->Build(data, size, builder->param)) { + HILOG_ERROR(LOG_CORE, "build data failed, name %{public}s", builder->name ?: "NULL"); + return false; + } + if (!(builder->nextBuilder)) { + return true; + } + return PurgMemBuilderBuildAll(builder->nextBuilder, data, size); +} + +bool PurgMemBuilderAppendBuilder(struct PurgMemBuilder *builder, struct PurgMemBuilder *newcomer) +{ + IF_NULL_LOG_ACTION(builder, "input builder is NULL", return false); + IF_NULL_LOG_ACTION(newcomer, "input newcomer is NULL", return false); + + AppendBuilder_(builder, newcomer); + return true; +} + +/* append a guest builder @newcomer to @head */ +static void AppendBuilder_(struct PurgMemBuilder *head, struct PurgMemBuilder *newcomer) +{ + if (!head->nextBuilder) { + head->nextBuilder = newcomer; + return; + } + return AppendBuilder_(head->nextBuilder, newcomer); +} diff --git a/libpurgeablemem/src/purgeable_mem.c b/libpurgeablemem/src/purgeable_mem.c new file mode 100644 index 0000000..43cb976 --- /dev/null +++ b/libpurgeablemem/src/purgeable_mem.c @@ -0,0 +1,427 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include /* size_t */ +#include /* malloc */ +#include /* mmap */ +#include +#include /* FILE */ +#include + +#include "pm_builder.h" +#include "pm_ptr_util.h" +#include "pm_state.h" +#include "ux_page_table.h" +#include "purgeable_mem.h" + +#define MAP_PURGEABLE 0x40 + +/* + * When UXPT is not used, In order not to affect the normal function + * of user programs, this lib will provide normal anon memory. So + * MAP_PURGEABLE is set to 0x0. + */ +#if (USE_UXPT == false) +#undef MAP_PURGEABLE +#define MAP_PURGEABLE 0x0 +#endif + +#undef LOG_TAG +#define LOG_TAG "libpurgeablemem" + +struct PurgMem { + void *dataPtr; + size_t dataSizeInput; + struct PurgMemBuilder *builder; + struct UxPageTable *uxPageTable; + pthread_rwlock_t rwlock; + unsigned int buildDataCount; +}; + +static const size_t PAGE_SHIFT = 12; +static const size_t PAGE_SIZE = 1 << PAGE_SHIFT; + +static inline void LogPurgMemInfo_(struct PurgMem *obj) +{ + HILOG_INFO(LOG_CORE, "purgMemObj(%{public}lx) dataPtr(%{public}lx) dataSizeInput(%{public}zu)" + " builderPtr(%{public}lx) uxpt(%{public}lx)", + (unsigned long)obj, (unsigned long)(obj->dataPtr), obj->dataSizeInput, + (unsigned long)(obj->builder), (unsigned long)(obj->uxPageTable)); +} + +static inline size_t RoundUp_(size_t val, size_t align) +{ + if (align == 0) { + return val; + } + return ((val + align - 1) / align) * align; +} + +static bool IsPurgMemPtrValid_(struct PurgMem *purgObj); +static bool IsPurged_(struct PurgMem *purgObj); + +static struct PurgMem *PurgMemCreate_(size_t len, struct PurgMemBuilder *builder) +{ + /* PurgMemObj allow no builder temporaily */ + struct PurgMem *pugObj = NULL; + pugObj = (struct PurgMem *)malloc(sizeof(struct PurgMem)); + if (!pugObj) { + HILOG_ERROR(LOG_CORE, "%{public}s: malloc struct PurgMem fail", __func__); + return NULL; + } + size_t size = RoundUp_(len, PAGE_SIZE); + pugObj->dataPtr = mmap(NULL, size, PROT_READ | PROT_WRITE, + MAP_PURGEABLE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (!pugObj->dataPtr) { + HILOG_ERROR(LOG_CORE, "%{public}s: mmap dataPtr fail", __func__); + goto free_pug_obj; + } + + pugObj->uxPageTable = (struct UxPageTable *)malloc(UxPageTableSize()); + if (!(pugObj->uxPageTable)) { + HILOG_ERROR(LOG_CORE, "%{public}s: malloc struct UxPageTable fail", __func__); + goto unmap_data; + } + PMState err = InitUxPageTable(pugObj->uxPageTable, pugObj->dataPtr, size); /* dataPtr is aligned */ + if (err != PM_OK) { + HILOG_ERROR(LOG_CORE, "%{public}s: InitUxPageTable fail, %{public}s", __func__, PMStateNames[err]); + goto free_uxpt; + } + int lockInitRet = pthread_rwlock_init(&(pugObj->rwlock), NULL); + if (lockInitRet) { + HILOG_ERROR(LOG_CORE, "%{public}s: pthread_rwlock_init fail, %{public}d", __func__, lockInitRet); + goto deinit_upt; + } + pugObj->builder = builder; + pugObj->dataSizeInput = len; + pugObj->buildDataCount = 0; + + HILOG_INFO(LOG_CORE, "%{public}s: LogPurgMemInfo_:", __func__); + LogPurgMemInfo_(pugObj); + return pugObj; + +deinit_upt: + DeinitUxPageTable(pugObj->uxPageTable); +free_uxpt: + free(pugObj->uxPageTable); + pugObj->uxPageTable = NULL; +unmap_data: + munmap(pugObj->dataPtr, size); + pugObj->dataPtr = NULL; +free_pug_obj: + free(pugObj); + pugObj = NULL; + + return NULL; +} + +struct PurgMem *PurgMemCreate(size_t len, PurgMemModifyFunc func, void *funcPara) +{ + if (!len) { + HILOG_ERROR(LOG_CORE, "%{public}s: input len 0", __func__); + return NULL; + } + + struct PurgMem *purgMemObj = PurgMemCreate_(len, NULL); + /* no build func or create fail */ + if (!func || !purgMemObj) { + /* this PurgMemObj allow no builder temporaily */ + return purgMemObj; + } + + if (PurgMemAppendModify(purgMemObj, func, funcPara)) { + return purgMemObj; + } + + /* append func fail meas create builder failed */ + HILOG_ERROR(LOG_CORE, "%{public}s: append mod func fail", __func__); + if (!PurgMemDestroy(purgMemObj)) { + HILOG_ERROR(LOG_CORE, "%{public}s: destroy PurgMem fail after append modFunc fail", __func__); + } + return NULL; +} + +bool PurgMemDestroy(struct PurgMem *purgObj) +{ + IF_NULL_LOG_ACTION(purgObj, "input is NULL", return true); + HILOG_INFO(LOG_CORE, "%{public}s: LogPurgMemInfo_:", __func__); + LogPurgMemInfo_(purgObj); + + PMState err = PM_OK; + /* destroy rwlock */ + int ret = pthread_rwlock_destroy(&(purgObj->rwlock)); + if (ret) { + HILOG_ERROR(LOG_CORE, "%{public}s: pthread_rwlock_destroy fail, %{public}d", __func__, ret); + } + /* destroy builder */ + if (purgObj->builder) { + if (!PurgMemBuilderDestroy(purgObj->builder)) { + HILOG_ERROR(LOG_CORE, "%{public}s: PurgMemBuilderDestroy fail", __func__); + err = PMB_DESTORY_FAIL; + } else { + purgObj->builder = NULL; + } + } + /* unmap purgeable mem region */ + if (purgObj->dataPtr) { + size_t size = RoundUp_(purgObj->dataSizeInput, PAGE_SIZE); + if (munmap(purgObj->dataPtr, size)) { + HILOG_ERROR(LOG_CORE, "%{public}s: munmap dataPtr fail", __func__); + err = PM_UNMAP_PURG_FAIL; + } else { + /* double check munmap result: if uxpte is set to no_present */ + if (UxpteIsEnabled() && !IsPurged_(purgObj)) { + HILOG_ERROR(LOG_CORE, "%{public}s: munmap dataPtr succ, but uxpte present", __func__); + err = PM_UXPT_PRESENT_DATA_PURGED; + } + purgObj->dataPtr = NULL; + } + } + /* unmap uxpt */ + if (purgObj->uxPageTable) { + PMState deinitRet = DeinitUxPageTable(purgObj->uxPageTable); + if (deinitRet != PM_OK) { + HILOG_ERROR(LOG_CORE, "%{public}s: deinit upt fail, %{public}s", __func__, PMStateNames[deinitRet]); + err = deinitRet; + } else { + free(purgObj->uxPageTable); + purgObj->uxPageTable = NULL; + } + } + + if (err == PM_OK) { + free(purgObj); + purgObj = NULL; /* set input para NULL to avoid UAF */ + HILOG_ERROR(LOG_CORE, "%{public}s: succ", __func__); + return true; + } + HILOG_ERROR(LOG_CORE, "%{public}s: fail, %{public}s", __func__, PMStateNames[err]); + return false; +} + +static bool IsPurgMemPtrValid_(struct PurgMem *purgObj) +{ + IF_NULL_LOG_ACTION(purgObj, "obj is NULL", return false); + IF_NULL_LOG_ACTION(purgObj->dataPtr, "dataPtr is NULL", return false); + IF_NULL_LOG_ACTION(purgObj->uxPageTable, "pageTable is NULL", return false); + IF_NULL_LOG_ACTION(purgObj->builder, "builder is NULL", return false); + + return true; +} + +static inline bool PurgMemBuildData_(struct PurgMem *purgObj) +{ + bool succ = false; + succ = PurgMemBuilderBuildAll(purgObj->builder, purgObj->dataPtr, purgObj->dataSizeInput); + if (succ) { + purgObj->buildDataCount++; + } + return succ; +} + +static PMState TryBeginRead_(struct PurgMem *purgObj) +{ + int rwlockRet = pthread_rwlock_rdlock(&(purgObj->rwlock)); + if (rwlockRet) { + HILOG_ERROR(LOG_CORE, "%{public}s: rdlock fail. %{public}d", __func__, rwlockRet); + return PM_LOCK_READ_FAIL; + } + + if (!IsPurged_(purgObj)) { + HILOG_INFO(LOG_CORE, "%{public}s: not purged, return true. MAP_PUG=0x%{public}x", __func__, MAP_PURGEABLE); + return PM_DATA_NO_PURGED; + } + + rwlockRet = pthread_rwlock_unlock(&(purgObj->rwlock)); + if (rwlockRet) { + HILOG_ERROR(LOG_CORE, "%{public}s: rd unlock fail. %{public}d", __func__, rwlockRet); + return PM_UNLOCK_READ_FAIL; + } + + return PM_DATA_PURGED; +} + +static PMState BeginReadBuildData_(struct PurgMem *purgObj) +{ + bool rebuildRet = false; + int rwlockRet = pthread_rwlock_wrlock(&(purgObj->rwlock)); + if (rwlockRet) { + HILOG_ERROR(LOG_CORE, "%{public}s: wrlock fail. %{public}d", __func__, rwlockRet); + return PM_LOCK_WRITE_FAIL; + } + + if (IsPurged_(purgObj)) { + rebuildRet = PurgMemBuildData_(purgObj); + HILOG_ERROR(LOG_CORE, "%{public}s: purged, after built %{public}s", __func__, rebuildRet ? "succ" : "fail"); + } + + rwlockRet = pthread_rwlock_unlock(&(purgObj->rwlock)); + if (rwlockRet) { + HILOG_ERROR(LOG_CORE, "%{public}s: wr unlock fail. %{public}d", __func__, rwlockRet); + return PM_UNLOCK_WRITE_FAIL; + } + + if (!rebuildRet) { + return PMB_BUILD_ALL_FAIL; + } + + return PMB_BUILD_ALL_SUCC; +} + +bool PurgMemBeginRead(struct PurgMem *purgObj) +{ + if (!IsPurgMemPtrValid_(purgObj)) { + HILOG_ERROR(LOG_CORE, "%{public}s: para is invalid", __func__); + return false; + } + HILOG_INFO(LOG_CORE, "%{public}s: LogPurgMemInfo_:", __func__); + LogPurgMemInfo_(purgObj); + bool ret = false; + PMState err = PM_OK; + UxpteGet(purgObj->uxPageTable, purgObj->dataPtr, purgObj->dataSizeInput); + while (true) { + err = TryBeginRead_(purgObj); + if (err == PM_DATA_NO_PURGED) { + ret = true; + break; + } else if (err != PM_DATA_PURGED) { + break; + } + + err = BeginReadBuildData_(purgObj); + if (err != PMB_BUILD_ALL_SUCC) { + ret = false; + break; + } + } + + if (!ret) { + HILOG_ERROR(LOG_CORE, "%{public}s: %{public}s, UxptePut.", __func__, PMStateNames[err]); + UxptePut(purgObj->uxPageTable, purgObj->dataPtr, purgObj->dataSizeInput); + } + return ret; +} + +bool PurgMemBeginWrite(struct PurgMem *purgObj) +{ + if (!IsPurgMemPtrValid_(purgObj)) { + HILOG_ERROR(LOG_CORE, "%{public}s: para is invalid", __func__); + return false; + } + HILOG_INFO(LOG_CORE, "%{public}s: LogPurgMemInfo_:", __func__); + LogPurgMemInfo_(purgObj); + int rwlockRet = 0; + bool rebuildRet = false; + PMState err = PM_OK; + + UxpteGet(purgObj->uxPageTable, purgObj->dataPtr, purgObj->dataSizeInput); + + rwlockRet = pthread_rwlock_wrlock(&(purgObj->rwlock)); + if (rwlockRet) { + HILOG_ERROR(LOG_CORE, "%{public}s: wrlock fail. %{public}d", __func__, rwlockRet); + err = PM_LOCK_WRITE_FAIL; + goto uxpte_put; + } + + if (!IsPurged_(purgObj)) { + goto succ; + } + + /* data is purged */ + rebuildRet = PurgMemBuildData_(purgObj); + HILOG_INFO(LOG_CORE, "%{public}s: purged, built %{public}s", __func__, rebuildRet ? "succ" : "fail"); + if (rebuildRet) { + goto succ; + } + /* data is purged and rebuild failed. return false */ + err = PMB_BUILD_ALL_FAIL; + rwlockRet = pthread_rwlock_unlock(&(purgObj->rwlock)); + if (rwlockRet) { + HILOG_ERROR(LOG_CORE, "%{public}s: wr unlock fail. %{public}d", __func__, rwlockRet); + } + +uxpte_put: + HILOG_ERROR(LOG_CORE, "%{public}s: %{public}s, return false, UxptePut.", __func__, PMStateNames[err]); + UxptePut(purgObj->uxPageTable, purgObj->dataPtr, purgObj->dataSizeInput); + return false; +succ: + return true; +} + +static inline void EndAccessPurgMem_(struct PurgMem *purgObj) +{ + if (!IsPurgMemPtrValid_(purgObj)) { + HILOG_ERROR(LOG_CORE, "%{public}s: para is invalid", __func__); + return; + } + int rwlockRet = 0; + rwlockRet = pthread_rwlock_unlock(&(purgObj->rwlock)); + if (rwlockRet) { + HILOG_ERROR(LOG_CORE, "%{public}s: unlock fail. %{public}d", __func__, rwlockRet); + } + UxptePut(purgObj->uxPageTable, purgObj->dataPtr, purgObj->dataSizeInput); +} + +void PurgMemEndRead(struct PurgMem *purgObj) +{ + EndAccessPurgMem_(purgObj); +} + +void PurgMemEndWrite(struct PurgMem *purgObj) +{ + EndAccessPurgMem_(purgObj); +} + +void *PurgMemGetContent(struct PurgMem *purgObj) +{ + if (!IsPurgMemPtrValid_(purgObj)) { + HILOG_ERROR(LOG_CORE, "%{public}s: para is invalid", __func__); + return NULL; + } + return purgObj->dataPtr; +} + +size_t PurgMemGetContentSize(struct PurgMem *purgObj) +{ + if (!IsPurgMemPtrValid_(purgObj)) { + HILOG_ERROR(LOG_CORE, "%{public}s: para is invalid", __func__); + return 0; + } + return purgObj->dataSizeInput; +} + +bool PurgMemAppendModify(struct PurgMem *purgObj, PurgMemModifyFunc func, void *funcPara) +{ + IF_NULL_LOG_ACTION(func, "input func is NULL", return true); + + struct PurgMemBuilder *builder = PurgMemBuilderCreate(func, funcPara, NULL); + IF_NULL_LOG_ACTION(builder, "PurgMemBuilderCreate fail", return false); + + if (purgObj->builder == NULL) { /* PurgMemObj has no builder previous */ + purgObj->builder = builder; + return true; + } + return PurgMemBuilderAppendBuilder(purgObj->builder, builder); +} + +static bool IsPurged_(struct PurgMem *purgObj) +{ + /* first access, return true means purged */ + if (purgObj->buildDataCount == 0) { + HILOG_INFO(LOG_CORE, "%{public}s, has never built, return true", __func__); + return true; + } + return !UxpteIsPresent(purgObj->uxPageTable, purgObj->dataPtr, purgObj->dataSizeInput); +} diff --git a/libpurgeablemem/src/ux_page_table.c b/libpurgeablemem/src/ux_page_table.c new file mode 100644 index 0000000..ad79d2b --- /dev/null +++ b/libpurgeablemem/src/ux_page_table.c @@ -0,0 +1,317 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include /* NULL */ +#include /* mmap */ +#include /* sched_yield() */ + +#include "hilog/log.h" +#include "ux_page_table.h" + +#undef LOG_TAG +#define LOG_TAG "libpurgeablemem: uxpt" + +#if (USE_UXPT == true) /* (USE_UXPT == true) means using uxpt */ + +struct UxPageTable { + uint64_t dataAddr; + size_t dataSize; + uxpte_t *uxpte; +}; + +/* + * ------------------------------------------------------------------------- + * | virtual page number | | + * |--------------------------------------------| vaddr offset in virt page | + * | uxpte page number | offset in uxpte page | | + * -------------------------------------------------------------------------- + * | | UXPTE_PER_PAGE_SHIFT | PAGE_SHIFT | + */ +static const size_t PAGE_SHIFT = 12; +static const size_t PAGE_SIZE = 1 << PAGE_SHIFT; +static const size_t UXPTE_SIZE_SHIFT = 3; +static const size_t UXPTE_PER_PAGE_SHIFT = PAGE_SHIFT - UXPTE_SIZE_SHIFT; +static const size_t UXPTE_PER_PAGE = 1 << UXPTE_PER_PAGE_SHIFT; + +/* get virtual page number from virtual address */ +static inline uint64_t VirtPageNo_(uint64_t vaddr) +{ + return vaddr >> PAGE_SHIFT; +} + +/* page number in user page table of uxpte for virtual address */ +static inline uint64_t UxptePageNo_(uint64_t vaddr) +{ + return VirtPageNo_(vaddr) >> UXPTE_PER_PAGE_SHIFT; +} + +/* uxpte offset in uxpte page for virtual address */ +static inline uint64_t UxpteOffset_(uint64_t vaddr) +{ + return VirtPageNo_(vaddr) & (UXPTE_PER_PAGE - 1); +} + +static const size_t UXPTE_PRESENT_BIT = 1; +static const size_t UXPTE_PRESENT_MASK = (1 << UXPTE_PRESENT_BIT) - 1; +static const size_t UXPTE_REFCNT_ONE = 1 << UXPTE_PRESENT_BIT; +static uxpte_t UXPTE_UNDER_RECLAIM = (uxpte_t)(-UXPTE_REFCNT_ONE); + +static inline bool IsUxptePresent_(uxpte_t pte) +{ + return pte & (uxpte_t)UXPTE_PRESENT_MASK; +} + +static inline bool IsUxpteUnderReclaim_(uxpte_t pte) +{ + return pte == UXPTE_UNDER_RECLAIM; +} + +static inline size_t GetUxPageSize_(uint64_t dataAddr, size_t dataSize) +{ + return (UxptePageNo_(dataAddr + dataSize - 1) - UxptePageNo_(dataAddr) + 1) * PAGE_SIZE; +} + +static inline uint64_t RoundUp_(uint64_t val, size_t align) +{ + if (align == 0) { + return val; + } + return ((val + align - 1) / align) * align; +} + +static inline uint64_t RoundDown_(uint64_t val, size_t align) +{ + if (align == 0) { + return val; + } + return val & (~(align - 1)); +} + +enum UxpteOp { + UPT_GET = 0, + UPT_PUT = 1, + UPT_IS_PRESENT = 2, +}; + +static void UxpteAdd_(uxpte_t *pte, size_t incNum); +static void UxpteSub_(uxpte_t *pte, size_t decNum); + +static void GetUxpteAt_(struct UxPageTable *upt, uint64_t addr); +static void PutUxpteAt_(struct UxPageTable *upt, uint64_t addr); +static bool IsPresentAt_(struct UxPageTable *upt, uint64_t addr); +static PMState UxpteOps_(struct UxPageTable *upt, uint64_t addr, size_t len, enum UxpteOp op); + +static uxpte_t *MapUxptePages_(uint64_t dataAddr, size_t dataSize); +static int UnmapUxptePages_(uxpte_t *ptes, size_t size); + +bool UxpteIsEnabled(void) +{ + return true; +} + +size_t UxPageTableSize(void) +{ + return sizeof(struct UxPageTable); +} + +PMState InitUxPageTable(struct UxPageTable *upt, void *addr, size_t len) +{ + upt->dataAddr = (uint64_t) addr; + upt->dataSize = len; + upt->uxpte = MapUxptePages_(upt->dataAddr, upt->dataSize); + if (!(upt->uxpte)) { + return PM_MMAP_UXPT_FAIL; + } + return PM_OK; +} + +PMState DeinitUxPageTable(struct UxPageTable *upt) +{ + size_t size = GetUxPageSize_(upt->dataAddr, upt->dataSize); + int unmapRet = 0; + if (upt->uxpte) { + unmapRet = UnmapUxptePages_(upt->uxpte, size); + if (unmapRet) { + HILOG_ERROR(LOG_CORE, "%{public}s: unmap uxpt fail", __func__); + return PM_UNMAP_UXPT_FAIL; + } + upt->uxpte = NULL; + } + upt->dataAddr = 0; + upt->dataSize = 0; + return PM_OK; +} + +void UxpteGet(struct UxPageTable *upt, void *addr, size_t len) +{ + UxpteOps_(upt, (uint64_t)addr, len, UPT_GET); +} + +void UxptePut(struct UxPageTable *upt, void *addr, size_t len) +{ + UxpteOps_(upt, (uint64_t)addr, len, UPT_PUT); +} + +bool UxpteIsPresent(struct UxPageTable *upt, void *addr, size_t len) +{ + PMState ret = UxpteOps_(upt, (uint64_t)addr, len, UPT_IS_PRESENT); + return ret == PM_OK; +} + +static void UxpteAdd_(uxpte_t *pte, size_t incNum) +{ + uxpte_t old = 0, ret = 0; + + while (true) { + old = *pte; + if (IsUxpteUnderReclaim_(old)) { + sched_yield(); + continue; + } + + ret = __sync_val_compare_and_swap(pte, old, old + incNum); + if (ret == old) + break; + } +} + +static void UxpteSub_(uxpte_t *pte, size_t decNum) +{ + (void)__sync_fetch_and_sub(pte, decNum); +} + +static inline size_t GetIndexInUxpte_(uint64_t startAddr, uint64_t currAddr) +{ + return UxpteOffset_(startAddr) + (VirtPageNo_(currAddr) - VirtPageNo_(startAddr)); +} + +static void GetUxpteAt_(struct UxPageTable *upt, uint64_t addr) +{ + size_t index = GetIndexInUxpte_(upt->dataAddr, addr); + UxpteAdd_(&(upt->uxpte[index]), UXPTE_REFCNT_ONE); + + HILOG_DEBUG(LOG_CORE, "%{public}s: addr(0x%{public}llx) upte=0x%{public}llx", + __func__, (unsigned long long)addr, (unsigned long long)(upt->uxpte[index])); +} + +static void PutUxpteAt_(struct UxPageTable *upt, uint64_t addr) +{ + size_t index = GetIndexInUxpte_(upt->dataAddr, addr); + UxpteSub_(&(upt->uxpte[index]), UXPTE_REFCNT_ONE); + + HILOG_DEBUG(LOG_CORE, "%{public}s: addr(0x%{public}llx) upte=0x%{public}llx", + __func__, (unsigned long long)addr, (unsigned long long)(upt->uxpte[index])); +} + +static bool IsPresentAt_(struct UxPageTable *upt, uint64_t addr) +{ + size_t index = GetIndexInUxpte_(upt->dataAddr, addr); + + HILOG_DEBUG(LOG_CORE, "%{public}s: addr(0x%{public}llx) upte=0x%{public}llx PRESENT_MASK=0x%{public}zx", + __func__, (unsigned long long)addr, (unsigned long long)(upt->uxpte[index]), UXPTE_PRESENT_MASK); + + return IsUxptePresent_(upt->uxpte[index]); +} + +static PMState UxpteOps_(struct UxPageTable *upt, uint64_t addr, size_t len, enum UxpteOp op) +{ + uint64_t start = RoundDown_(addr, PAGE_SIZE); + uint64_t end = RoundUp_(addr + len, PAGE_SIZE); + if (start < upt->dataAddr || end > (upt->dataAddr + upt->dataSize)) { + HILOG_ERROR(LOG_CORE, "%{public}s: addr(0x%{public}llx) start(0x%{public}llx) < dataAddr(0x%{public}llx)" + " || end(0x%{public}llx) > dataAddr+dataSize(0x%{public}llx) out of bound", + __func__, (unsigned long long)addr, (unsigned long long)start, (unsigned long long)(upt->dataAddr), + (unsigned long long)end, (unsigned long long)(upt->dataAddr + upt->dataSize)); + + return PM_UXPT_OUT_RANGE; + } + + for (uint64_t off = start; off < end; off += PAGE_SIZE) { + switch (op) { + case UPT_GET: { + GetUxpteAt_(upt, off); + break; + } + case UPT_PUT: { + PutUxpteAt_(upt, off); + break; + } + case UPT_IS_PRESENT: { + if (!IsPresentAt_(upt, off)) { + HILOG_ERROR(LOG_CORE, "%{public}s: addr(0x%{public}llx) not present", __func__, + (unsigned long long)addr); + return PM_UXPT_NO_PRESENT; + } + break; + } + default: + break; + } + } + + return PM_OK; +} + +static uxpte_t *MapUxptePages_(uint64_t dataAddr, size_t dataSize) +{ + int prot = PROT_READ | PROT_WRITE; + int type = MAP_PRIVATE | MAP_ANONYMOUS | MAP_USEREXPTE; + size_t size = GetUxPageSize_(dataAddr, dataSize); + uxpte_t *ptes = (uxpte_t*)mmap(NULL, size, prot, type, -1, UxptePageNo_(dataAddr) * PAGE_SIZE); + if (ptes == (void *)-1) { + HILOG_ERROR(LOG_CORE, "%{public}s: fail, return NULL", __func__); + ptes = NULL; + } + + return ptes; +} + +static int UnmapUxptePages_(uxpte_t *ptes, size_t size) +{ + return munmap(ptes, size); +} + +#else /* (USE_UXPT == false), it means does not using uxpt */ + +bool UxpteIsEnabled(void) +{ + return false; +} + +size_t UxPageTableSize(void) +{ + return 0; +} + +PMState InitUxPageTable(struct UxPageTable *upt, void *addr, size_t len) +{ + return PM_OK; +} + +PMState DeinitUxPageTable(struct UxPageTable *upt) +{ + return PM_OK; +} + +void UxpteGet(struct UxPageTable *upt, void *addr, size_t len) {} + +void UxptePut(struct UxPageTable *upt, void *addr, size_t len) {} + +bool UxpteIsPresent(struct UxPageTable *upt, void *addr, size_t len) +{ + return true; +} + +#endif /* USE_UXPT == true */ diff --git a/libpurgeablemem/test/BUILD.gn b/libpurgeablemem/test/BUILD.gn new file mode 100644 index 0000000..59fa7ad --- /dev/null +++ b/libpurgeablemem/test/BUILD.gn @@ -0,0 +1,45 @@ +# Copyright (C) 2022 Huawei Device Co., Ltd. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import("//build/ohos.gni") +import("//build/ohos_var.gni") +import("//build/test.gni") + +module_output_path = "libpurgeablemem/" + +purgeable_deps = [ + "//base/hiviewdfx/hilog/interfaces/native/innerkits:libhilog", + "//utils/memory/libpurgeablemem:libpurgeablemem", +] + +purgeable_external_deps = [ "hiviewdfx_hilog_native:libhilog" ] + +purgeable_public_deps = [ "//third_party/googletest:gtest_main" ] + +ohos_unittest("purgeable_c_test") { + module_out_path = module_output_path + sources = [ "purgeable_c_test.cpp" ] + deps = purgeable_deps + if (is_standard_system) { + external_deps = purgeable_external_deps + public_deps = purgeable_public_deps + } + + subsystem_name = "utils" + part_name = "utils_memory" +} + +group("libpurgeablemem_test") { + testonly = true + deps = [ ":purgeable_c_test" ] +} diff --git a/libpurgeablemem/test/purgeable_c_test.cpp b/libpurgeablemem/test/purgeable_c_test.cpp new file mode 100644 index 0000000..1802eae --- /dev/null +++ b/libpurgeablemem/test/purgeable_c_test.cpp @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include "gtest/gtest.h" +#include "purgeable_mem.h" + +namespace { +using namespace testing; +using namespace testing::ext; + +struct AlphabetInitParam { + char start; + char end; +}; + +struct AlphabetModifyParam { + char src; + char dst; +}; + +static constexpr int PRINT_INTERVAL_SECONDS = 1; +static constexpr int RECLAIM_INTERVAL_SECONDS = 1; +static constexpr int MODIFY_INTERVAL_SECONDS = 2; + +bool InitData_(void *data, size_t size, char start, char end); +bool ModifyData_(void *data, size_t size, char src, char dst); +bool InitAlphabet(void *data, size_t size, void *param); +bool ModifyAlphabetX2Y(void *data, size_t size, void *param); +void LoopPrintAlphabet(struct PurgMem *pdata, unsigned int loopCount); +bool ReclaimPurgeable(void); +void LoopReclaimPurgeable(unsigned int loopCount); +void ModifyPurgMemByFunc(struct PurgMem *pdata, PurgMemModifyFunc Modfunc, void *param); + +class PurgeableCTest : public testing::Test { +public: + static void SetUpTestCase(); + static void TearDownTestCase(); + void SetUp(); + void TearDown(); +}; + +void PurgeableCTest::SetUpTestCase() +{ +} + +void PurgeableCTest::TearDownTestCase() +{ +} + +void PurgeableCTest::SetUp() +{ +} + +void PurgeableCTest::TearDown() +{ +} + +HWTEST_F(PurgeableCTest, MultiObjCreateTest, TestSize.Level1) +{ + const char alphabetFinal[] = "BBCDEFGHIJKLMNOPQRSTUVWXYZ\0"; + struct AlphabetInitParam initPara = {'A', 'Z'}; + struct PurgMem *pobj1 = PurgMemCreate(27, InitAlphabet, &initPara); + LoopPrintAlphabet(pobj1, 1); + struct AlphabetModifyParam a2b = {'A', 'B'}; + ModifyPurgMemByFunc(pobj1, ModifyAlphabetX2Y, (void *)&a2b); + LoopPrintAlphabet(pobj1, 1); + LoopReclaimPurgeable(1); + + struct PurgMem *pobj2 = PurgMemCreate(27, InitAlphabet, &initPara); + LoopPrintAlphabet(pobj2, 1); + ModifyPurgMemByFunc(pobj2, ModifyAlphabetX2Y, (void *)&a2b); + LoopPrintAlphabet(pobj2, 1); + + if (PurgMemBeginRead(pobj1)) { + ASSERT_STREQ(alphabetFinal, (char *)PurgMemGetContent(pobj1)); + PurgMemEndRead(pobj1); + } else { + std::cout << __func__ << ": ERROR! BeginRead failed." << std::endl; + } + + if (PurgMemBeginRead(pobj2)) { + ASSERT_STREQ(alphabetFinal, (char *)PurgMemGetContent(pobj2)); + PurgMemEndRead(pobj2); + } else { + std::cout << __func__ << ": ERROR! BeginRead failed." << std::endl; + } + + PurgMemDestroy(pobj2); + PurgMemDestroy(pobj1); +} + +HWTEST_F(PurgeableCTest, ReadTest, TestSize.Level1) +{ + const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\0"; + struct AlphabetInitParam initPara = {'A', 'Z'}; + struct PurgMem *pobj = PurgMemCreate(27, InitAlphabet, &initPara); + std::thread reclaimThread(LoopReclaimPurgeable, (unsigned int)(-1)); + pthread_t reclaimPid = reclaimThread.native_handle(); + reclaimThread.detach(); + + unsigned int loopCount = 3; + /* loop read content */ + for (unsigned int i = 0; i < loopCount; i++) { + if (!PurgMemBeginRead(pobj)) { + std::cout << __func__ << ": ERROR! BeginRead failed." << std::endl; + continue; + } + ASSERT_STREQ(alphabet, (char *)PurgMemGetContent(pobj)); + PurgMemEndRead(pobj); + } + + pthread_cancel(reclaimPid); /* destroy reclaimThread */ + PurgMemDestroy(pobj); +} + +HWTEST_F(PurgeableCTest, WriteTest, TestSize.Level1) +{ + const char alphabet[] = "CCCDEFGHIJKLMNOPQRSTUVWXYZ\0"; + struct AlphabetInitParam initPara = {'A', 'Z'}; + struct PurgMem *pobj = PurgMemCreate(27, InitAlphabet, &initPara); + std::thread reclaimThread(LoopReclaimPurgeable, (unsigned int)(-1)); + pthread_t reclaimPid = reclaimThread.native_handle(); + reclaimThread.detach(); + + struct AlphabetModifyParam a2b = {'A', 'B'}; + struct AlphabetModifyParam b2c = {'B', 'C'}; + ModifyPurgMemByFunc(pobj, ModifyAlphabetX2Y, (void *)&a2b); + ModifyPurgMemByFunc(pobj, ModifyAlphabetX2Y, (void *)&b2c); + + if (PurgMemBeginRead(pobj)) { + ASSERT_STREQ(alphabet, (char *)PurgMemGetContent(pobj)); + PurgMemEndRead(pobj); + } else { + std::cout << __func__ << ": ERROR! BeginRead failed." << std::endl; + } + + pthread_cancel(reclaimPid); /* destroy reclaimThread */ + PurgMemDestroy(pobj); + LoopReclaimPurgeable(3); +} + +HWTEST_F(PurgeableCTest, ReadWriteTest, TestSize.Level1) +{ + const char alphabet[] = "DDDDEFGHIJKLMNOPQRSTUVWXYZ\0"; + struct AlphabetInitParam initPara = {'A', 'Z'}; + struct PurgMem *pobj = PurgMemCreate(27, InitAlphabet, &initPara); + /* loop reclaim thread */ + std::thread reclaimThread(LoopReclaimPurgeable, (unsigned int)(-1)); + pthread_t reclaimPid = reclaimThread.native_handle(); + reclaimThread.detach(); + /* loop read thread */ + std::thread readThread(LoopPrintAlphabet, pobj, (unsigned int)(-1)); + pthread_t readPid = readThread.native_handle(); + readThread.detach(); + + struct AlphabetModifyParam a2b = {'A', 'B'}; + struct AlphabetModifyParam b2c = {'B', 'C'}; + struct AlphabetModifyParam c2d = {'C', 'D'}; + ModifyPurgMemByFunc(pobj, ModifyAlphabetX2Y, (void *)&a2b); + ModifyPurgMemByFunc(pobj, ModifyAlphabetX2Y, (void *)&b2c); + ModifyPurgMemByFunc(pobj, ModifyAlphabetX2Y, (void *)&c2d); + + if (PurgMemBeginRead(pobj)) { + ASSERT_STREQ(alphabet, (char *)PurgMemGetContent(pobj)); + PurgMemEndRead(pobj); + } else { + std::cout << __func__ << ": ERROR! BeginRead failed." << std::endl; + } + + pthread_cancel(reclaimPid); /* destroy reclaimThread */ + pthread_cancel(readPid); /* destroy readThread */ + PurgMemDestroy(pobj); +} + +bool InitData_(void *data, size_t size, char start, char end) +{ + char *str = (char *)data; + size_t len = 0; + for (char ch = start; ch <= end && len < size; ch++) { + str[len++] = ch; + } + str[len] = 0; + return true; +} + +bool InitAlphabet(void *data, size_t size, void *param) +{ + struct AlphabetInitParam *para = (struct AlphabetInitParam *)param; + std::cout << "inter " << __func__ << std::endl; + bool ret = InitData_(data, size, para->start, para->end); + std::cout << "quit " << __func__ << ": " << para->start << "-" << para->end << + ", data=[" << (char *)data << "]" << ", ret=" << (ret ? "true" : "false") << std::endl; + return ret; +} + +bool ModifyData_(void *data, size_t size, char src, char dst) +{ + char *str = (char *)data; + size_t i = 0; + for (; str[i] && i < size; i++) { + if (str[i] == src) { + str[i] = dst; + } + } + str[i] = 0; + return true; +} + +bool ModifyAlphabetX2Y(void *data, size_t size, void *param) +{ + struct AlphabetModifyParam *para = (struct AlphabetModifyParam *)param; + std::cout << "inter " << __func__ << ": " << para->src << "->" << para->dst << + ", data=[" << (char *)data << "]" << std::endl; + bool ret = ModifyData_(data, size, para->src, para->dst); + std::cout << "quit , data=[" << (char *)data << "]" << __func__ << + ", ret=" << (ret ? "true" : "false") << std::endl; + return ret; +} + +void LoopPrintAlphabet(struct PurgMem *pdata, unsigned int loopCount) +{ + std::cout << "inter " << __func__ << std::endl; + for (unsigned int i = 0; i < loopCount; i++) { + if (!PurgMemBeginRead(pdata)) { + std::cout << __func__ << ": " << i << ". ERROR! BeginRead failed." << std::endl; + break; + } + std::cout << __func__ << ": " << i << ". data=[" << + (char *)PurgMemGetContent(pdata) << "]" << std::endl; + PurgMemEndRead(pdata); + std::this_thread::sleep_for(std::chrono::seconds(PRINT_INTERVAL_SECONDS)); + } + std::cout << "quit " << __func__ << std::endl; +} + +bool ReclaimPurgeable(void) +{ + FILE *f = fopen("/proc/sys/kernel/purgeable", "w"); + if (!f) { + std::cout << __func__ << ": open file failed" << std::endl; + return false; + } + bool succ = true; + if (fputs("1", f) == EOF) { + succ = false; + } + + if (fclose(f) == EOF) { + std::cout << __func__ << ": close file failed" << std::endl; + } + + return succ; +} + +void LoopReclaimPurgeable(unsigned int loopCount) +{ + bool ret = false; + std::cout << "inter " << __func__ << std::endl; + for (unsigned int i = 0; i < loopCount; i++) { + ret = ReclaimPurgeable(); + std::cout << __func__ << ": " << i << ". Reclaim result=" << (ret ? "succ" : "fail") << std::endl; + std::this_thread::sleep_for(std::chrono::seconds(RECLAIM_INTERVAL_SECONDS)); /* wait reclaim finish */ + } + std::cout << "quit " << __func__ << std::endl; +} + +void ModifyPurgMemByFunc(struct PurgMem *pdata, PurgMemModifyFunc Modfunc, void *param) +{ + if (!PurgMemBeginWrite(pdata)) { + std::cout << __func__ << ": ERROR! BeginWrite failed." << std::endl; + return; + } + std::this_thread::sleep_for(std::chrono::seconds(MODIFY_INTERVAL_SECONDS)); + std::cout << __func__ << " before mod data=[" << (char *)PurgMemGetContent(pdata) << "]" << std::endl; + Modfunc(PurgMemGetContent(pdata), PurgMemGetContentSize(pdata), param); + std::cout<< __func__ << " after mod data=[" << (char *)PurgMemGetContent(pdata) << "]" << std::endl; + PurgMemAppendModify(pdata, Modfunc, param); + + std::cout << __func__ << " data=[" << (char *)PurgMemGetContent(pdata) << "]" << std::endl; + PurgMemEndWrite(pdata); +} +} /* namespace */ -- Gitee