rk3368 Android9.0 HIDL除錯記錄
rk3368 Android9.0 HIDL除錯記錄
Platform: RK3368
OS: Android 9.0
Kernel: 4.4.194
文章目錄
在Android 8.0以後,低層已重新編寫以採用更加模組化的新架構。必須支援使用 HIDL 語言編寫的 HAL,下面列出了一些例外情況。這些 HAL 可以是繫結式 HAL 也可以是直通式 HAL.
繫結式 HAL 以 HAL 介面定義語言 (HIDL) 或 Android 介面定義語言 (AIDL) 表示的 HAL。這些 HAL 取代了早期 Android 版本中使用的傳統 HAL 和舊版 HAL。在繫結式 HAL 中,Android 框架和 HAL 之間通過 Binder 程式間通訊 (IPC) 呼叫進行通訊。所有在推出時即搭載了 Android 8.0 或更高版本的裝置都必須只支援繫結式 HAL。
直通式 HAL 以 HIDL 封裝的傳統 HAL 或舊版 HAL。這些 HAL 封裝了現有的 HAL,可在繫結模式和 Same-Process(直通)模式下使用。升級到 Android 8.0 的裝置可以使用直通式 HAL。
此除錯記錄為繫結式 HAL;
1. 使用hidl-gen工具生成介面
1.1 hidl-gen使用方法
$ hidl-gen -h
usage: hidl-gen [-p <root path>] -o <output path> -L <language> [-O <owner>] (-r <interface root>)+ [-v] [-d <depfile>] FQNAME...
Process FQNAME, PACKAGE(.SUBPACKAGE)*@[0-9]+.[0-9]+(::TYPE)?, to create output.
-h: Prints this menu.
-L <language>: The following options are available:
check : Parses the interface to see if valid but doesn't write any files.
c++ : (internal) (deprecated) Generates C++ interface files for talking to HIDL interfaces.
c++-headers : (internal) Generates C++ headers for interface files for talking to HIDL interfaces.
c++-sources : (internal) Generates C++ sources for interface files for talking to HIDL interfaces.
export-header : Generates a header file from @export enumerations to help maintain legacy code.
c++-impl : Generates boilerplate implementation of a hidl interface in C++ (for convenience).
c++-impl-headers: c++-impl but headers only
c++-impl-sources: c++-impl but sources only
c++-adapter : Takes a x.(y+n) interface and mocks an x.y interface.
c++-adapter-headers: c++-adapter but helper headers only
c++-adapter-sources: c++-adapter but helper sources only
c++-adapter-main: c++-adapter but the adapter binary source only
java : (internal) Generates Java library for talking to HIDL interfaces in Java.
java-constants : (internal) Like export-header but for Java (always created by -Lmakefile if @export exists).
vts : (internal) Generates vts proto files for use in vtsd.
makefile : (removed) Used to generate makefiles for -Ljava and -Ljava-constants.
androidbp : (internal) Generates Soong bp files for -Lc++-headers, -Lc++-sources, -Ljava, -Ljava-constants, and -Lc++-adapter.
androidbp-impl : Generates boilerplate bp files for implementation created with -Lc++-impl.
hash : Prints hashes of interface in `current.txt` format to standard out.
-O <owner>: The owner of the module for -Landroidbp(-impl)?.
-o <output path>: Location to output files.
-p <root path>: Android build root, defaults to $ANDROID_BUILD_TOP or pwd.
-r <package:path root>: E.g., android.hardware:hardware/interfaces.
-v: verbose output.
-d <depfile>: location of depfile to write to.
1.2 建立HIDL檔案
- 新建目錄vendor/sample/hardware/interfaces/helloworld/1.0
- 建立檔案: IHelloWorld.hal IHelloWorldCallback.hal types.hal
不要直接在Android原始碼目錄hardware/interfaces下面新增和修改介面,在API鎖定分支中不允許更改VNDK庫列表.
其中vendor/sample/hardware/interfaces就是hidl的root path了.
helloworld/1.0/IHelloWorld.hal
package sample.hardware.helloworld@1.0;
import IHelloWorldCallback;
interface IHelloWorld {
initial();
getInt() generates (int32_t i);
setInt(int32_t val) generates (Result error);
oneway setCallback(IHelloWorldCallback callback);
};
helloworld/1.0/IHelloWorldCallback.hal
package sample.hardware.helloworld@1.0;
interface IHelloWorldCallback {
oneway onEvent(Event event);
};
helloworld/1.0/types.hal
package sample.hardware.helloworld@1.0;
enum Result : int32_t {
OK,
UNKNOWN,
INVALID_ARGUMENTS,
};
struct Event {
uint32_t type;
uint32_t code;
uint32_t value;
};
- 指定HIDL包根目錄
新增檔案vendor/sample/hardware/interfaces/Android.bp
hidl_package_root {
name: "sample.hardware",
path: "vendor/sample/hardware/interfaces",
}
如果不指定HIDL包根目錄,編譯會報錯:Cannot find package root specification
interfaces: Cannot find package root specification for package root 'sample.hardware' needed for module 'sample.hardware.helloworld@1.0'. Either this is a mispelling of the package root, or a new hidl_package_root module needs to be added. For example, you can fix this error by adding the following to <some path>/Android.bp:
hidl_package_root {
name: "sample.hardware",
path: "<some path>",
}
- 生成HIDL雜湊
每個軟體包根目錄(即對映到 hardware/interfaces 的 android.hardware 或對映到 vendor/foo/hardware/interfaces 的 vendor.foo)都必須包含一個列出所有已釋出 HIDL 介面檔案的 current.txt 檔案。
$ hidl-gen -Lhash -rsample.hardware:vendor/sample/hardware/interfaces -randroid.hidl:system/libhidl/transport sample.hardware.helloworld@1.0> vendor/sample/hardware/interfaces/current.txt
1.3 使用hidl-gen工具生成HIDL相關檔案
- 使用hidl-gen生成C++檔案
$ hidl-gen -o vendor/sample/hardware/interfaces/helloworld/1.0/default -Lc++-impl -rsample.hardware:vendor/sample/hardware/interfaces -randroid.hidl:system/libhidl/transport sample.hardware.helloworld@1.0
把生成的下面兩個不用的檔案刪除:
helloworld/1.0/default/HelloWorldCallback.cpp
helloworld/1.0/default/HelloWorldCallback.h
- 使用hidl-gen生成helloworld/1.0/default/Android.bp
$ hidl-gen -o vendor/sample/hardware/interfaces/helloworld/1.0/default -Landroidbp-impl -rsample.hardware:vendor/sample/hardware/interfaces -randroid.hidl:system/libhidl/transport sample.hardware.helloworld@1.0
- 使用system/tools/hidl/update-makefiles-helper.sh生成helloworld/1.0/Android.bp
$ source $ANDROID_BUILD_TOP/system/tools/hidl/update-makefiles-helper.sh
$ do_makefiles_update sample.hardware:vendor/sample/hardware/interfaces android.hardware:hardware/interfaces android.hidl:system/libhidl/transport
helloworld/1.0/Android.bp內容如下:
// This file is autogenerated by hidl-gen -Landroidbp.
hidl_interface {
name: "sample.hardware.helloworld@1.0",
root: "sample.hardware",
srcs: [
"types.hal",
"IHelloWorld.hal",
"IHelloWorldCallback.hal",
],
interfaces: [
"android.hidl.base@1.0",
],
types: [
"Event",
"Result",
],
gen_java: true,
}
- 為device manifest新增hal介面
由hwservicemanager去解析並將服務啟動;
device/rockchip/$TARGET_PRODUCT/manifest.xml
<hal format="hidl">
<name>sample.hardware.helloworld</name>
<transport>hwbinder</transport>
<version>1.0</version>
<interface>
<name>IHelloWorld</name>
<instance>default</instance>
</interface>
</hal>
然後完整編譯一下Android,更新manifest.xml檔案:
out/target/product/$TARGET_PRODUCT/vendor/etc/vintf/manifest.xml
如果不更新device manifest,測試程式會找不到服務端,hwservicemanager會報以下錯誤:
W hwservicemanager: getTransport: Cannot find entry sample.hardware.helloworld@1.0::IHelloWorld/default in either framework or device manifest.
2. 修改程式碼
2.1 建立service程式碼
hardware/interfaces/sample/1.0/default/service.cpp
#define LOG_TAG "sample.hardware.helloworld@1.0-service"
#include <android-base/logging.h>
#include <hidl/HidlTransportSupport.h>
#include "HelloWorld.h"
using android::OK;
using android::sp;
using android::status_t;
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;
using sample::hardware::helloworld::V1_0::IHelloWorld;
using sample::hardware::helloworld::V1_0::implementation::HelloWorld;
int main(int /* argc */, char ** /* argv */)
{
ALOGD("HAL Service is starting.");
sp<IHelloWorld> service = new HelloWorld();
configureRpcThreadpool(1, true /*callerWillJoin*/);
status_t status = service->registerAsService();
if (status != OK)
{
LOG_ALWAYS_FATAL("Could not register service for HelloWorld HAL Iface (%d).", status);
return -1;
}
ALOGD("Register as service ready.");
joinRpcThreadpool();
return 1; // joinRpcThreadpool shouldn't exit
}
2.2 修改Android.bp新增編譯service
hardware/interfaces/sample/1.0/default/Android.bp
將cc_library_shared節點proprietary: true改為vendor: true;
刪除relative_install_path: “hw”,目的是將sample.hardware.helloworld@1.0-impl.so編譯安裝到vendor/lib64目錄下面,如果vendor/lib64/hw下面,執行服務時因VNDK規則限制,會報以下錯誤:
F linker : CANNOT LINK EXECUTABLE “./vendor/bin/hw/sample.hardware.helloworld@1.0-service”: library “sample.hardware.helloworld@1.0-impl.so” not found
cc_library_shared {
name: "sample.hardware.helloworld@1.0-impl",
vendor: true,
srcs: [
"HelloWorld.cpp",
],
shared_libs: [
"liblog",
"libhidlbase",
"libhidltransport",
"libutils",
"sample.hardware.helloworld@1.0",
],
}
cc_binary {
name: "sample.hardware.helloworld@1.0-service",
defaults: ["hidl_defaults"],
vendor: true,
relative_install_path: "hw",
init_rc: ["sample.hardware.helloworld@1.0-service.rc"],
srcs: ["service.cpp"],
shared_libs: [
"libcutils",
"libhidlbase",
"libhidltransport",
"liblog",
"libutils",
"libhardware",
"sample.hardware.helloworld@1.0",
"sample.hardware.helloworld@1.0-impl",
],
}
2.3 新增啟動service的init.rc檔案
hardware/interfaces/sample/1.0/default/android.hardware.sample@1.0-service.rc
service vendor.helloworld-1-0 /vendor/bin/hw/sample.hardware.helloworld@1.0-service
class hal
user system
group system
2.4 修改生成的程式碼
helloworld/1.0/default/HelloWorld.h
#ifndef SAMPLE_HARDWARE_HELLOWORLD_V1_0_HELLOWORLD_H
#define SAMPLE_HARDWARE_HELLOWORLD_V1_0_HELLOWORLD_H
#include <sample/hardware/helloworld/1.0/IHelloWorld.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
namespace sample {
namespace hardware {
namespace helloworld {
namespace V1_0 {
namespace implementation {
using ::android::hardware::hidl_array;
using ::android::hardware::hidl_memory;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::sp;
struct HelloWorld : public IHelloWorld
{
Return<void> initial() override;
Return<int32_t> getInt() override;
Return<::sample::hardware::helloworld::V1_0::Result> setInt(int32_t val) override;
Return<void> setCallback(const sp<::sample::hardware::helloworld::V1_0::IHelloWorldCallback> &callback) override;
static void *pollThreadWrapper(void *me);
void pollThreadEntry();
private:
pthread_mutex_t mLock = PTHREAD_MUTEX_INITIALIZER;
pthread_t mPollThread;
bool mRunning;
std::vector<sp<IHelloWorldCallback>> mCallbacks;
};
} // namespace implementation
} // namespace V1_0
} // namespace helloworld
} // namespace hardware
} // namespace sample
#endif // SAMPLE_HARDWARE_HELLOWORLD_V1_0_HELLOWORLD_H
helloworld/1.0/default/HelloWorld.cpp
#define LOG_TAG "sample.hardware.helloworld@1.0-service"
#include <android-base/logging.h>
#include <log/log.h>
#include "HelloWorld.h"
namespace sample {
namespace hardware {
namespace helloworld {
namespace V1_0 {
namespace implementation {
Return<void> HelloWorld::initial()
{
ALOGD("%s", __FUNCTION__);
if (mRunning)
return Void();
mRunning = true;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
pthread_create(&mPollThread, &attr, pollThreadWrapper, this);
pthread_attr_destroy(&attr);
return Void();
}
Return<int32_t> HelloWorld::getInt()
{
ALOGD("%s", __FUNCTION__);
return int32_t{666};
}
Return<::sample::hardware::helloworld::V1_0::Result> HelloWorld::setInt(int32_t val)
{
ALOGD("%s %d", __FUNCTION__, val);
return ::sample::hardware::helloworld::V1_0::Result{};
}
void *HelloWorld::pollThreadWrapper(void *me)
{
static_cast<HelloWorld *>(me)->pollThreadEntry();
return NULL;
}
void HelloWorld::pollThreadEntry()
{
mRunning = true;
ALOGD("%s enter", __FUNCTION__);
Event event;
event.type = 0;
event.code = 0;
event.value = 0;
while (mRunning)
{
event.code++;
event.type++;
event.value++;
sleep(1);
ALOGD("%s event %04x %04x %04x\n", __FUNCTION__, event.type, event.code, event.value);
pthread_mutex_lock(&mLock);
if (!mCallbacks.empty())
{
std::vector<sp<IHelloWorldCallback>>::iterator it;
for (it = mCallbacks.begin(); it != mCallbacks.end();)
{
sp<IHelloWorldCallback> callback = *it;
Return<void> ret = callback->onEvent(event);
if (!ret.isOk())
{
ALOGE("error %s", ret.description().c_str());
it = mCallbacks.erase(it);
}
else
{
it++;
}
}
}
pthread_mutex_unlock(&mLock);
}
ALOGD("%s exit", __FUNCTION__);
}
Return<void> HelloWorld::setCallback(const sp<::sample::hardware::helloworld::V1_0::IHelloWorldCallback> &callback)
{
ALOGD("%s", __FUNCTION__);
pthread_mutex_lock(&mLock);
if (callback != nullptr)
{
mCallbacks.push_back(callback);
}
pthread_mutex_unlock(&mLock);
return Void();
}
} // namespace implementation
} // namespace V1_0
} // namespace helloworld
} // namespace hardware
} // namespace sample
2.5 新增本地C++測試程式碼
helloworld/1.0/test/Android.bp
cc_binary {
name: "sample.hardware.helloworld_hidl_hal_test",
vendor: true,
srcs: ["test.cpp"],
shared_libs: [
"libhidlbase",
"libhidltransport",
"liblog",
"libutils",
"libhardware",
"sample.hardware.helloworld@1.0",
],
}
helloworld/1.0/test/test.cpp
#define LOG_TAG "sample.hardware.helloworld_hidl_hal_test"
#include <android-base/logging.h>
#include <hidl/HidlTransportSupport.h>
#include <log/log.h>
#include <sample/hardware/helloworld/1.0/IHelloWorld.h>
#include <sample/hardware/helloworld/1.0/types.h>
using ::android::sp;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::sample::hardware::helloworld::V1_0::Event;
using ::sample::hardware::helloworld::V1_0::IHelloWorld;
using ::sample::hardware::helloworld::V1_0::IHelloWorldCallback;
using ::sample::hardware::helloworld::V1_0::Result;
class HelloWorldCallback : public IHelloWorldCallback
{
public:
HelloWorldCallback()
{
printf("%s\n", __FUNCTION__);
}
~HelloWorldCallback()
{
printf("%s\n", __FUNCTION__);
}
Return<void> onEvent(const Event &event)
{
printf("%s %04x %04x %04x\n", __FUNCTION__, event.type, event.code, event.value);
return Void();
}
};
int main(int /* argc */, char ** /* argv */)
{
sp<IHelloWorld> service = IHelloWorld::getService();
if (service == nullptr)
{
printf("Failed to get service\n");
return -1;
}
printf("initial\n");
service->initial();
int ret = service->getInt();
printf("getInt %d\n", ret);
Result result = service->setInt(888);
printf("setInt result=%d\n", result);
sp<IHelloWorldCallback> callback = new HelloWorldCallback();
printf("setCallback\n");
service->setCallback(callback);
while (1)
{
sleep(1);
}
return 0;
}
3. 編譯測試
3.1 mmm單獨模組編譯:
$ mmm vendor/sample/hardware/interfaces/helloworld/1.0/
- 編譯hal檔案生成的中間程式碼位於out/soong/.intermediates/vendor/sample/hardware/interfaces/helloworld/1.0/:
default
sample.hardware.helloworld@1.0
sample.hardware.helloworld@1.0-adapter
sample.hardware.helloworld@1.0-adapter_genc++
sample.hardware.helloworld@1.0-adapter-helper
sample.hardware.helloworld@1.0-adapter-helper_genc++
sample.hardware.helloworld@1.0-adapter-helper_genc++_headers
sample.hardware.helloworld@1.0_genc++
sample.hardware.helloworld@1.0_genc++_headers
sample.hardware.helloworld-V1.0-java
sample.hardware.helloworld-V1.0-java_gen_java
test
"sample.hardware.helloworld@1.0_genc++"目錄裡面可以發現hal檔案轉換成了C++原始碼 ,裡面就包含Binder Bn端,Binder Bp端的程式碼.不用像非Project Treble專案那樣需要自己寫Binder Bn端,Binder Bp端的程式碼.體會到了谷歌的良苦用心.
- 編譯最終會在out/target/product/$TARGET_PRODUCT/目錄下面生成了以下檔案:
out/target/product/$TARGET_PRODUCT/system/lib/sample.hardware.helloworld@1.0.so
out/target/product/$TARGET_PRODUCT/system/lib/sample.hardware.helloworld@1.0-adapter-helper.so
out/target/product/$TARGET_PRODUCT/system/lib64/sample.hardware.helloworld@1.0.so
out/target/product/$TARGET_PRODUCT/system/lib64/sample.hardware.helloworld@1.0-adapter-helper.so
out/target/product/$TARGET_PRODUCT/system/framework/sample.hardware.helloworld-V1.0-java.jar
out/target/product/$TARGET_PRODUCT/system/framework/oat/arm/sample.hardware.helloworld-V1.0-java.odex
out/target/product/$TARGET_PRODUCT/system/framework/oat/arm/sample.hardware.helloworld-V1.0-java.vdex
out/target/product/$TARGET_PRODUCT/system/framework/oat/arm64/sample.hardware.helloworld-V1.0-java.odex
out/target/product/$TARGET_PRODUCT/system/framework/oat/arm64/sample.hardware.helloworld-V1.0-java.vdex
out/target/product/$TARGET_PRODUCT/vendor/lib/sample.hardware.helloworld@1.0-impl.so
out/target/product/$TARGET_PRODUCT/vendor/lib/sample.hardware.helloworld@1.0.so
out/target/product/$TARGET_PRODUCT/vendor/lib/sample.hardware.helloworld@1.0-adapter-helper.so
out/target/product/$TARGET_PRODUCT/vendor/lib64/sample.hardware.helloworld@1.0-impl.so
out/target/product/$TARGET_PRODUCT/vendor/lib64/sample.hardware.helloworld@1.0.so
out/target/product/$TARGET_PRODUCT/vendor/lib64/sample.hardware.helloworld@1.0-adapter-helper.so
out/target/product/$TARGET_PRODUCT/vendor/bin/hw/sample.hardware.helloworld@1.0-service
out/target/product/$TARGET_PRODUCT/vendor/bin/sample.hardware.helloworld_hidl_hal_test
out/target/product/$TARGET_PRODUCT/vendor/etc/init/sample.hardware.helloworld@1.0-service.rc
3.2 Native測試
- 將編譯好的庫和bin檔案複製到裝置中:
$ adb root
$ adb remount
$ adb push out/target/product/$TARGET_PRODUCT/vendor/lib64/sample.hardware.helloworld@1.0-impl.so /vendor/lib64/sample.hardware.helloworld@1.0-impl.so
$ adb push out/target/product/$TARGET_PRODUCT/vendor/lib64/sample.hardware.helloworld@1.0.so /vendor/lib64/sample.hardware.helloworld@1.0.so
$ adb push out/target/product/$TARGET_PRODUCT/vendor/bin/hw/sample.hardware.helloworld@1.0-service /vendor/bin/hw/sample.hardware.helloworld@1.0-service
$ adb push out/target/product/$TARGET_PRODUCT/vendor/bin/sample.hardware.helloworld_hidl_hal_test /vendor/bin/sample.hardware.helloworld_hidl_hal_test
- 啟動服務和測試
# ./vendor/bin/hw/sample.hardware.helloworld@1.0-service&
# sample.hardware.helloworld_hidl_hal_test
initial
getInt 666
setInt result=0
HelloWorldCallback
setCallback
onEvent 0001 0001 0001
onEvent 0002 0002 0002
onEvent 0003 0003 0003
3.3 Android APP java測試
Android Studio中將out/soong/.intermediates/vendor/sample/hardware/interfaces/helloworld/1.0/sample.hardware.helloworld-V1.0-java/android_common/combined/sample.hardware.helloworld-V1.0-java.jar 複製到APP工程的libs目錄下面,然後將此jar包匯入工程就可以使用了,java使用就是這麼簡單,連JNI都不用自己寫了;
不要用/system/framework/sample.hardware.helloworld-V1.0-java.jar這個jar包,這是Dex優化剝離了class檔案後的jar;
package com.example.helloworld;
import android.app.Activity;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import sample.hardware.helloworld.V1_0.Event;
import sample.hardware.helloworld.V1_0.IHelloWorld;
import sample.hardware.helloworld.V1_0.IHelloWorldCallback;
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try {
IHelloWorld helloWorld = IHelloWorld.getService();
helloWorld.initial();
int ret = helloWorld.getInt();
Log.d(TAG, "getInt=" + ret);
helloWorld.setInt(888);
helloWorld.setCallback(new HelloWorldCallback());
} catch (RemoteException e) {
e.printStackTrace();
}
}
static class HelloWorldCallback extends IHelloWorldCallback.Stub {
@Override
public void onEvent(Event event) throws RemoteException {
Log.d(TAG, "onEvent type=" + event.type + ", code=" + event.code + ", value=" + event.value);
}
}
}
相關文章
- 指紋適配記錄Android9.0Android
- GDB除錯使用記錄除錯
- Python 學習除錯記錄Python除錯
- GitHub學習除錯記錄Github除錯
- Supervisor 安裝除錯記錄除錯
- FCoE測試重啟除錯記錄除錯
- [翻譯] 除錯 Rxjs(二):日誌記錄除錯JS
- 記錄一次非常麻煩的除錯除錯
- 除錯一記除錯
- Android除錯命令收錄Android除錯
- stm32學習之除錯篇踩坑記錄除錯
- [20230329]記錄除錯sql語句遇到的問題.txt除錯SQL
- hidl實現hal
- 深夜除錯某瀏覽器記憶體損壞的小記錄除錯瀏覽器記憶體
- JSP筆記-除錯JS筆記除錯
- mongodb 報錯記錄MongoDB
- SpringMVC錯誤記錄SpringMVC
- Navicat Oracle 刪除使用者錯誤ora-01922 個人記錄Oracle
- Communications--1--資料傳輸除錯記錄-bug採坑雷區除錯
- gdb除錯學習與實踐記錄 -- 常用命令解析1除錯
- 運維除錯記錄:Ubuntu14.04下部署Opendaylight Nitrogen叢集運維除錯Ubuntu
- NodeMCU入門:燒錄、除錯、聯網除錯
- 除錯備忘錄-SWD協議解析除錯協議
- mysql刪除一條記錄MySql
- 記錄Electron打包報錯
- C++錯誤記錄C++
- 【隨筆記】T507 ADC SGM58031 16BIT 4Channel 除錯記錄筆記除錯
- 除錯篇——除錯物件與除錯事件除錯物件事件
- windows10更新記錄刪除_怎樣刪除win10更新歷史記錄WindowsWin10
- 刪除重複id的記錄
- 記憶體洩漏除錯工具記憶體除錯
- 筆記|軟體除錯的技巧筆記除錯
- Node除錯指南-記憶體篇除錯記憶體
- ESP32S3 N16R8, USB_STREAM UVC除錯記錄S3除錯
- android nfc tag3 除錯日記Android除錯
- VS斷點除錯簡單筆記斷點除錯筆記
- Windows windbg kernel debug 雙機核心除錯 - USB3.0 除錯 USB除錯 除錯線Windows除錯
- tensorflow錯誤記錄:tf.concat