Android Tombstone 與Debuggerd 原理淺談

一、前言

Android系統類問題主要有stabilityperformancepowersecurityAndroid集成一個守護進程tombstonedandroid平臺的一個守護進程,它注冊成3socket服務端,客戶端封裝在crash_dumpdebuggerd_client crash_dump用于跟蹤定位C++ crash debuggerd_client用于在某些場景(發生ANRwatchdogshell執行debuggerd -bdump指定進程 backtrace

二、tombstone原理

1,ELF程序加載過程

在進入execve()系統調用之后,Linux內核就開始進行真正的裝配工作。在內核中,execve()系統調用相應的入口是sys_execve()sys_execve()進行一些參數的檢查復制之后,調用do_execve()do_execve()會首先查找被執行的文件,如果找到文件,則讀取文件的前128個字節。文件的前128個字節保存著可執行文件的格式信息,特別是前四個字節(魔數)。這樣可以根據不同的可執行文件信息,來調用不同的裝載模塊。當do_execve()讀取了這128個字節的文件頭部之后,然后調用search_binary_handle()去搜索和匹配合適的可執行文件裝載處理。linux中所有被支持的可執行文件格式都有相應的裝載處理過程。search_binary_handle()會通過判斷文件頭部的魔數確定文件的格式,并且調用相應的過程。ELF可執行文件的裝載處理過程叫做load_elf_binary()load_elf_binary()被定義在fs/Binfmt_elf.c。它的主要步驟是:

1. 檢查ELF可執行文件格式的有效性,比如魔數,程序頭表中段(Segment)的數量

2. 尋找動態鏈接的“.interp段,設置動態連接器路徑(與動態鏈接有關)

3. 根據ELF可執行文件的程序頭表的描述,對ELF文件進行映射,比如代碼、數據、只讀數據。

4. 初始化ELF進程環境,比如進程啟動時EDX寄存器的地址應該是DT_FINI的地址。

5. 將系統調用的返回地址修改成ELF可執行文件的入口點,這個入口點取決于程序的鏈接方式,對于靜態鏈接的ELF可執行文件,這個程序入口就是ELF文件的文件頭中e_entry所指的地址;對于動態鏈接的ELF可執行文件,程序入口點是動態連接器。

當load_elf_binary()執行完畢,返回至do_execve()再返回到sys_execve(),上面的第5步中已經把系統調用的返回地址改成了被裝載的ELF程序的入口地址了。所以當sys_execve()系統調用從內核態返回到用戶態時,EIP寄存器存放下一個機器指令的地址, 直接跳轉到了ELF程序的入口地址了,于是新的程序開始執行,ELF可執行文件裝載完成。

execve() 執行過程中,系統會清掉 fork() 復制的原程序的頁目錄和頁表項,并釋放對應頁面。系統僅為新加載的程序代碼重新設置進程數據結構中的信息,申請和映射了命令行參數和環境參數塊所占的內存頁面,以及設置了執行代碼執行點。此時內核并不從執行文件所在塊設備上加載程序的代碼和數據。當該過程返回時即開始執行新的程序,但一開始執行肯定會引起缺頁異常中斷發生。因為代碼和數據還未被從塊設備上讀入內存。此時缺頁異常處理程序會根據引起異常的線性地址在主內存區為新程序申請內存頁面(內存幀),并從塊設備上讀入引起異常的指定頁面。同時還為該線性地址設置對應的頁目錄項和頁表項。這種加載執行文件的方法稱為需求加載(Load on demand)。

2,Android Linker

--dynamic-linker編譯參數的ELF文件會加載為靜態可執行程序,如init進程:

Android大部分可執行程序使用linker做動態連接器,編譯時候加上參數 --dynamic-linker

除了init進程,recoveryadbd進程也沒有配置--dynamic-linker鏈接器。他們在Android.mk里面聲明LOCAL_FORCE_STATIC_EXECUTABLE := true或者在Android.bp聲明static_executable: true。靜態可執行程序不能加載libc等動態庫,只能導入static_libs。以上都是針對ELF格式的程序。

當然,/system/bin/linker也是靜態連接的。他自己不能配置為動態鏈接器。LinkerAndroid.bp里面配置static_executable: true。同時根據arch編譯匯編代碼 begin.S來配置程序入口。Arm64匯編指令?bl:跳轉指令,但是在跳轉之前,會將下一條指令保存到返回地址(鏈接寄存器)LR寄存器中。Brbl類似,只是后面參數需要特定的寄存器。

3,Android Linker 攔截信號

應用程序注冊信號處理hanlder,當信號事件發生后,內核將信號置為pending狀態,在中斷返回或者系統調用返回時,查看pending的信號,內核在應用程序的棧上構建一個信號處理棧幀,然后通過中斷返回或者系統調用返回到用戶態,執行信號處理函數。執行信號處理函數之后,再次通過sigreturn系統調用返回到內核,在內核中再次返回到應用程序被中斷打斷的地方或者系統調用返回的地方接著運行。?

debuggerd_signal_handler()函數最開始使用互斥鎖 pthread_mutex_lock() 來保護線程,方式同一時間多個線程處理信號而導致沖突。接著,調用 log_signal_summary() 來輸出一些log 信息信息,例如fault addrsignosignamepidtid、線程名、主線程名等。

接著,調用clone() 函數創建偽線程,并在偽線程中調用?debuggerd_dispatch_pseudothread()?函數,原來的線程原地等待子線程的開始和結束。

如果應用程序沒有注冊對應的信號處理函數,那么信號發生后,內核按照內核默認的信號處理方式處理該信號。

debuggerd_dispatch_pseudothread()?線程中會 fork 一個子進程,并通過 execle() 系統調用去執行 crash_dump64 程序,父進程等待 crash_dump64 進程退出。

4,LINUX信號

當程序運行出現異常時候,CPU會發出指令異常信號。非可靠信號,由linker注冊。Linker加載的動態可執行程序,即使被加載程序調用了注冊函數函數,也不能正常收到,因為這些信號已經提前被被linker截斷。

中斷信號的產生有以下4個來源:1,外設(來自中斷控制器); 2,IPI(處理器間中斷); 3, CPU異常(比如前一條指令存在除零錯誤、缺頁錯誤等); 4,中斷指令。前兩種中斷都可以叫做硬件中斷,都是異步的;后兩種中斷都可以叫做軟件中斷,都是同步的。在arm64架構加,將上述中斷分類為異步異常(Physical interruptsVirtual interrupts)和同步異常(同步異常與當前指令的執行直接相關)。其實大同小異。以上是CPU層面的,對于LINUX來說,以上描述的異常或者中斷,都代表一次hardirq。當然hardirq可能產生一個softirq到軟中斷pending列表,或者直接被處理返回。Linux的軟中斷是進程調度層面的,由內核線程softirqd去讀取軟中斷pending列表,再轉化為信號分發。當一個進程收到一個信號的時候,進程會被掛起,開始執行中斷處理函數。Linux操作系統當中有62個信號,前31個(1-31):不可靠信號,非實時信號,信號有可能丟失,后31個(34-64):可靠信號,信號不會丟失。非可靠信號注冊多次,只會處理第一次注冊的,可靠信號注冊多次,會處理多次。Linux處理軟中斷處于程序上下文。處理硬中斷處于中斷上下文。

5,crash dump流程

crash_dump64 進程中,再fork 一個新進程,父進程通過 fork_exit_read 去等待子進程,子進程繼續執行 crash_dump 的任務。

crash_dump64 中,通過 /proc/PID/cmdline 獲取進程的名字,通過 /proc/PID/fd 獲取此進程打開多少文件,每個文件都有一個描述符。

crash_dump64 中循環遍歷這個進程中所有的線程,對進程中的每一個線程進行 ptrace 操作,對目標線程讀取器 crashinfocrashinfo 讀取完畢后 detech 當前的線程。

之后,在 crash_dump64 中調用?tombstoned_connect()?通過 socket 連接到 tombstoned 進程。根據 signal si_val 的值做不同的判斷,為0dump tombstone,為1dump backtrace

如果是 dump tombstone,最終 tombstone 通過?engrave_tombstone()?函數生成,engrave_tombstone() 函數的第二個參數?unwinder 是輸出 backtrace 等信息的關鍵函數。unwinder 初始化過程中獲取了當前進程的內存和 memory map,這些信息會在后面幫助 debuggerd 生成 tombstone 文件。

dump_memory_and_code()

打印寄存器附近的memory 信息。

dump_signal_info()?

函數打印引發這次 tombstone 的信號信息摘要。

dump_probable_cause()

通過分析 signal_info 打印可能的原因信息。如果沒有分析出可能的原因就不會打印出任何信息。

dump_abort_message()

通過內存信息,確定 abort message

dump_registers()

打印出錯時寄存器的值,thread_info 中記錄了錯誤發生時的寄存器信息。

log_backtrace()

調用棧。

dump_memory_and_code()

打印寄存器附近的memory 信息。

?dump_all_maps()

map 信息記錄了進程對應的內存映射,包括開始地址,長度,訪問權限,文件描述符,offset 等信息。

6,debug dump trace 流程

Debug dump的觸發場景一般是system_server發生watch dog,或者系統執行“debuggerd –b [pid]”命令產生。分別是調用進程加載組件debuggerd_client的接口:
dump_backtrace_to_file(),?
或者JNI接口:
android_os_Debug_dumpJavaBacktraceToFileTimeout(), android_os_Debug_dumpNativeBacktraceToFileTimeout()。
信號會對系統調用產生影響,觸發系統調用的自動重啟動。

debuggerd_trigger_dump向“tombstoned_intercept”發送InterceptRequest?請求,tombstone中的intercept_manager 處理該請求,并返回intercept的狀態,如register/started/failed等。

然后向目標進程發送信號signal = (dump_type == kDebuggerdJavaBacktrace) ? SIGQUIT : DEBUGGER_SIGNAL;? --- #define DEBUGGER_SIGNAL (__SIGRTMIN + 3)。

之后等待tombstoned的信息返回。

7,Crash dump與debug dump整體流程

異常發生時, ARM 處理器會跳轉到對應該異常的 固定地址 去執行異常處理程序, 這個?固定的地址?就是異常向量。對于tombstone類異常,會產生一個Software interrupt (SWI)。每個cpu會有一個內核線程softirqd輪詢讀取軟中斷,并把信號轉發給對應的應用。除了SWIARM還會產生ResetUndefined instructionsPrefetch Abort (instruction fetch memory abort)?Data Abort (data access memory abort)IRQ (interrupt)以及FIQ (fast interrupt)Arm64會產生:Synchronous exceptionSerrorIRQ FIQ四個類型的同步異常。不同架構中斷與異常代碼流程底層差異比較大。除了底層上報,還有各個應用程序可以給其他應用發送信號,由操作系統調度。

三、LOG解析

1,debugdump

一般問題不會是libc.so或者libhwbinder.so這種經過多年檢驗的公共庫。找到與問題版本對應的符號產物。本地復現的在符號在out/ 目錄里 symbols/ 目錄下(或者在編譯后的工程先source lunch之后再用addr2line?

addr2line -f –a -C -e

./symbols/vendor/bin/hw/android.hardware.audio@2.0-service? 00001771

2,crashdump

四、示例

1,權限問題導致SIGABRT

檢查main log,有如下selinux打印:

07-18 17:48:44.679  6105  6105 W Binder:6105_2: type=1400 audit(0.0:3532): avc: denied { read write } for name="hab" dev="tmpfs" ino=15391 scontext=u:r:shell:s0 tcontext=u:object_r:hab_device:s0 tclass=chr_file permissive=0
07-18 17:48:44.691  6105  6114 I Adreno-GSL_RPC: <gsl_rpc_connect:1211>: connecting using conn_id 0
07-18 17:48:44.692  6105  6114 I uhab    : habmm_socket_open: hab: open failed, error code 13 Permission denied
07-18 17:48:44.692  6105  6114 E Adreno-GSL_RPC: <gsl_hab_open:51>: Unable to habmm_socket_open err -13

后來修改:

allow shell hab_device:chr_file_rw_file_perms;

2,線程時序導致空指針異常

pid: 1082, tid: 1534, name: HwBinder:1082_1  >>> /system/vendor/bin/ipacm <<<
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x8
Cause: null pointer dereferencex0  0000000000000000  x1  0000000000000000  x2  6c206b636172746e  x3  0a2e646165726874x4  2072656e65747369  x5  000000719b41d399  x6  20746f4720202928  x7  7364662068746f62x8  edf87d25729f7502  x9  edf87d25729f7502  x10 0000000000004001  x11 0000000000000000x12 656e657473696c20  x13 000000000000009a  x14 0000000000000000  x15 000000719b98e3f8x16 000000719beb11e0  x17 000000719be44950  x18 0000000000000001  x19 0000000000000000x20 000000719b98f588  x21 0000000000000006  x22 000000719b98f588  x23 000000719b4327a8x24 000000719b48f000  x25 0000005fcc052680  x26 0000000000000000  x27 000000719b98f588x28 0000000000000000  x29 000000719b98e440sp  000000719b98e410  lr  0000005fcc052908  pc  0000005fcc0500c4backtrace:#00 pc 00000000000330c4  /vendor/bin/ipacm (IPACM_ConntrackListener::CreateConnTrackThreads()+36)#01 pc 0000000000035904  /vendor/bin/ipacm (IPACM_OffloadManager::provideFd(int, unsigned int)+644)#02 pc 000000000000a1d8  /vendor/lib64/liboffloadhal.so (HAL::setHandles(android::hardware::hidl_handle const&, android::hardware::hidl_handle const&, std::__1::function<void (bool, android::hardware::hidl_string const&)>)+420)#03 pc 000000000000ceec  /system/lib64/vndk-28/android.hardware.tetheroffload.config@1.0.so (android::hardware::tetheroffload::config::V1_0::BnHwOffloadConfig::_hidl_setHandles(android::hidl::base::V1_0::BnHwBase*, android::hardware::Parcel const&, android::hardware::Parcel*, std::__1::function<void (android::hardware::Parcel&)>)+316)#04 pc 000000000000d1a0  /system/lib64/vndk-28/android.hardware.tetheroffload.config@1.0.so (android::hardware::tetheroffload::config::V1_0::BnHwOffloadConfig::onTransact(unsigned int, android::hardware::Parcel const&, android::hardware::Parcel*, unsigned int, std::__1::function<void (android::hardware::Parcel&)>)+320)#05 pc 000000000001d334  /system/lib64/vndk-sp-28/libhwbinder.so (android::hardware::IPCThreadState::executeCommand(int)+640)#06 pc 000000000001430c  /system/lib64/vndk-sp-28/libhwbinder.so (android::hardware::IPCThreadState::getAndExecuteCommand()+196)#07 pc 0000000000014588  /system/lib64/vndk-sp-28/libhwbinder.so (android::hardware::IPCThreadState::joinThreadPool(bool)+268)#08 pc 000000000001c1ac  /system/lib64/vndk-sp-28/libhwbinder.so (android::hardware::PoolThread::threadLoop()+24)#09 pc 000000000000fa08  /system/lib64/vndk-sp-28/libutils.so (android::Thread::_threadLoop(void*)+280)#10 pc 00000000000819b4  /system/lib64/libc.so (__pthread_start(void*)+36)#11 pc 0000000000023478  /system/lib64/libc.so (__start_thread+68)

具體代碼就不多貼了,直接貼修改,就懂了。?

3,libart 出現BUS_ADRALN

pid: 2147, tid: 2147, name: m.app.animation  >>> com.app.animation <<<
signal 7 (SIGBUS), code 1 (BUS_ADRALN), fault addr 0x656c62x0  0000006f90ee8648  x1  0000000041504e4d  x2  0000007ff0cf5be8  x3  0000006f90668ed8x4  0000000012c12da6  x5  0000007ff0cf5c46  x6  6f00630015000000  x7  770067002e006d00x8  0000000000656c62  x9  0000006f90e4b700  x10 0000000000000032  x11 69006e0061002e00x12 6900740061006d00  x13 000000006e006f00  x14 0000000070ca1348  x15 0000000000003ebax16 0000006f90714140  x17 000000701265db30  x18 0000000000000000  x19 0000007ff0cf5be8x20 0000000041504e4d  x21 0000006f90e2b6f8  x22 0000006f90e2b6f8  x23 0000007ff0cf6088x24 0000007ff0cf5c14  x25 00000070168895e0  x26 000000007090b600  x27 0000000071178cb0x28 0000000012c12c20  x29 0000007ff0cf5bd0sp  0000007ff0cf5bb0  lr  0000006f90558a30  pc  0000000000656c62backtrace:#00 pc 0000000000656c62  <unknown>#01 pc 0000000000473a2c  /system/lib64/libart.so (art::RuntimeCallbacks::DdmPublishChunk(unsigned int, art::ArrayRef<unsigned char const> const&)+56)#02 pc 00000000003f0f14  /system/lib64/libart.so (art::DdmServer_nativeSendChunk(_JNIEnv*, _jclass*, int, _jbyteArray*, int, int)+356)#03 pc 000000000007fb74  /system/framework/arm64/boot-core-libart.oat (offset 0x79000) (org.apache.harmony.dalvik.ddmc.DdmServer.nativeSendChunk+196)#04 pc 000000000013e9bc  /system/framework/arm64/boot-core-libart.oat (offset 0x79000) (org.apache.harmony.dalvik.ddmc.DdmServer.sendChunk+76)#05 pc 00000000007e92ac  /system/framework/arm64/boot-framework.oat (offset 0x3d2000) (android.ddm.DdmHandleAppName.sendAPNM+284)#06 pc 0000000000879a78  /system/framework/arm64/boot-framework.oat (offset 0x3d2000) (android.app.ActivityThread.handleBindApplication+952)#07 pc 0000000000876034  /system/framework/arm64/boot-framework.oat (offset 0x3d2000) (android.app.ActivityThread$H.handleMessage+6916)#08 pc 0000000000ab7724  /system/framework/arm64/boot-framework.oat (offset 0x3d2000) (android.os.Handler.dispatchMessage+180)#09 pc 0000000000aba820  /system/framework/arm64/boot-framework.oat (offset 0x3d2000) (android.os.Looper.loop+1264)#10 pc 0000000000882e28  /system/framework/arm64/boot-framework.oat (offset 0x3d2000) (android.app.ActivityThread.main+664)#11 pc 0000000000554c4c  /system/lib64/libart.so (art_quick_invoke_static_stub+604)#12 pc 00000000000cf6e8  /system/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+232)#13 pc 000000000045c840  /system/lib64/libart.so (art::(anonymous namespace)::InvokeWithArgArray(art::ScopedObjectAccessAlreadyRunnable const&, art::ArtMethod*, art::(anonymous namespace)::ArgArray*, art::JValue*, char const*)+104)#14 pc 000000000045e294  /system/lib64/libart.so (art::InvokeMethod(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jobject*, _jobject*, unsigned long)+1440)#15 pc 00000000003ee1d4  /system/lib64/libart.so (art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*)+52)#16 pc 000000000011e6d4  /system/framework/arm64/boot-core-oj.oat (offset 0x114000) (java.lang.Class.getDeclaredMethodInternal [DEDUPED]+180)#17 pc 0000000000bf1bd8  /system/framework/arm64/boot-framework.oat (offset 0x3d2000) (com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run+136)#18 pc 0000000000bf8dc0  /system/framework/arm64/boot-framework.oat (offset 0x3d2000) (com.android.internal.os.ZygoteInit.main+3088)#19 pc 0000000000554c4c  /system/lib64/libart.so (art_quick_invoke_static_stub+604)#20 pc 00000000000cf6e8  /system/lib64/libart.so (art::ArtMethod::Invoke(art::Thread*, unsigned int*, unsigned int, art::JValue*, char const*)+232)#21 pc 000000000045c840  /system/lib64/libart.so (art::(anonymous namespace)::InvokeWithArgArray(art::ScopedObjectAccessAlreadyRunnable const&, art::ArtMethod*, art::(anonymous namespace)::ArgArray*, art::JValue*, char const*)+104)#22 pc 000000000045c4a0  /system/lib64/libart.so (art::InvokeWithVarArgs(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, std::__va_list)+424)#23 pc 0000000000361b60  /system/lib64/libart.so (art::JNI::CallStaticVoidMethodV(_JNIEnv*, _jclass*, _jmethodID*, std::__va_list)+652)#24 pc 00000000000b2374  /system/lib64/libandroid_runtime.so (_JNIEnv::CallStaticVoidMethod(_jclass*, _jmethodID*, ...)+120)#25 pc 00000000000b4cf8  /system/lib64/libandroid_runtime.so (android::AndroidRuntime::start(char const*, android::Vector<android::String8> const&, bool)+756)#26 pc 000000000000219c  /system/bin/app_process64 (main+1200)#27 pc 00000000000acac8  /system/lib64/libc.so (__libc_init+88)

由于每次異常打印的異常地址都是 fault addr 0x656c62,且多個應用都有打印,說明該問題是libart.so強相關,而不是com.app.animation的內存問題。地址0x656c62固定,有可能兩種情況,一種是代碼中存在字節不對齊情況,一種是這個地址是相對于對象首地址的偏移地址,這是對象被釋放后的解引用。

出現的原因是adbd守護進程會因為開機階段多次重新配置usb模式而被多次殺掉重新啟動。如果殺掉重新啟動adbd的時候正好碰上zygote孵化進程,某些進程就有概率報告這個錯誤。

解決方法:

Android12沒有該問題。Android12上面gStatethis_指針沒有delete, 他們使用C++17std::optional<>模板,無newdelete

4,system_server 調用 debug dump導致audio crash

?

五、其他

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/167291.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/167291.shtml
英文地址,請注明出處:http://en.pswp.cn/news/167291.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

前端入門(三)Vue生命周期、組件技術、事件總線、

文章目錄 Vue生命周期Vue 組件化編程 - .vue文件非單文件組件組件的注意點組件嵌套Vue實例對象和VueComponent實例對象Js對象原型與原型鏈Vue與VueComponent的重要內置關系 應用單文件組件構建 Vue腳手架 - vue.cli項目文件結構refpropsmixin插件scoped樣式 Vue生命周期 1、bef…

MBA-論證有效性分析

論證有效性分析∶分析下述論證中存在的缺陷和漏洞&#xff0c;選擇若干要點&#xff0c;寫一篇 600 字左石的文章.對該論證的有效性進行分析和評論。&#xff08;論證有效性分析的一般要點是∶概念特別是核心概念的界定和使用是否準確并前后一致&#xff0c;有無各種明顯的邏輯…

cineSync 3.3新功能: 深入iconik集成、激光工具、OTIOZ支持等

cineSync 3.3為大家帶來了靈活性和精準度&#xff0c;使連接審閱會話與iconik中的媒體管理和存儲更加容易&#xff0c;并且引入了顏色配置文件以快速測試顏色配置&#xff0c;還有通過激光指針等新工具帶來新的可能性。 在ftrack&#xff0c;我們意識到當今的遠程創意工作流比以…

vue3 導出數據為 excel 文件

文章目錄 安裝插件封裝組件 -- Export2Excel.js多表封裝界面使用 -- 數據處理成二維數組更多 菜鳥最近做了一個需求&#xff0c;就是需要上傳表單并識別&#xff0c;然后識別出來的內容要可以修改&#xff0c;然后想的就是識別內容變成 form 表單&#xff0c;所以并沒有使用 Sp…

反爬蟲機制與反爬蟲技術(二)

反爬蟲機制與反爬蟲技術二 1、動態頁面處理與驗證碼識別概述2、反爬蟲案例:頁面登錄與滑塊驗證碼處理2.1、用例簡介2.2、庫(模塊)簡介2.3、網頁分析2.4、Selenium準備操作2.5、頁面登錄2.6、模糊移動滑塊測試3、滑塊驗證碼處理:精確移動滑塊3.1、精確移動滑塊的原理3.2、滑…

【模塊補充】importlib

importlib 【一】介紹 importlib 模塊是 Python 中用于動態加載和導入模塊的內置模塊。它提供了一組函數和類&#xff0c;使得我們可以在運行時根據需要加載模塊&#xff0c;并且可以對已導入的模塊進行操作和管理。 【二】詳解及示例&#xff1a; 【1】動態加載模塊&#…

PyQt6簡介

鋒哥原創的PyQt6視頻教程&#xff1a; 2024版 PyQt6 Python桌面開發 視頻教程(無廢話版) 玩命更新中~_嗶哩嗶哩_bilibili2024版 PyQt6 Python桌面開發 視頻教程(無廢話版) 玩命更新中~共計12條視頻&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面開發 視頻教程(無廢話版…

企業遠程訪問業務系統:對比MPLS專線,貝銳蒲公英為何更優優勢?

如今&#xff0c;企業大多都會采用OA、ERP、CRM等各種數字化業務系統。 私有云、公有云混合架構也變得越來越常見。 比如&#xff1a;研發系統部署在公司本地私有云、確保數據安全&#xff0c;OA采用公有云方案、滿足隨時隨地訪問需求。 如此一來&#xff0c;也產生了遠程訪問…

js前端跨屏效果

效果: 三個球 源碼: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>三個球</title> </h…

js實現圖片懶加載

方式一&#xff1a;html實現 在img標簽加上 loading"lazy" 方式二&#xff1a;js實現 通過js監聽頁面的滾動&#xff0c;實現的原理主要是判斷當前圖片是否到了可視區域&#xff1a; 拿到所有的圖片 dom 。遍歷每個圖片判斷當前圖片是否到了可視區范圍內。如果到了…

Maven項目下詳細的SSM整合流程

文章目錄 &#x1f389;SSM整合流程一、兩個容器整合? 1、先準備好數據庫config.properties連接、mybatis-config.xml&#x1f38a; 2、容器一&#xff1a;優先配置spring.xml文件&#x1f38a; 3、容器二&#xff1a;配置springMVC.xml文件&#x1f38a; 4、Tomcat整合spring…

解釋PCIe MSI 中斷要求中斷向量連續?PCIe 規范里并沒有明確指出

MSI 向量必須連續&#xff1f; 前言 MSI 物理條件&#xff0c;MSI 中斷產生的邏輯是RC初始化的時候&#xff0c;由軟件將配置寫入到 EP 的 2 個寄存器中&#xff0c;這兩個寄存器一個指示的是地址 Message Address&#xff0c;一個指示的是數據 Message Data。當 EP 試圖觸發…

你再不學Git就來不及了!!!

其他系列文章導航 設計模式合集 多線程合集 分布式合集 ES合集 文章目錄 其他系列文章導航 文章目錄 前言 版本控制 什么是版本控制 為什么要版本控制 一、認識 Git 1.1Git 簡史 1.2Git 與其他版本管理系統的主要區別 1.3Git 的三種狀態 二、Git 使用快速入門 2.1獲…

springboot使用redis緩存亂碼(key或者 value 亂碼)一招解決

如果查看redis中的值是這樣 創建一個配置類就可以解決 package com.deka.config;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; i…

CPU+GPU多樣化算力,ZStack Cloud助力游戲精釀核心業務上云

游戲精釀通過ZStack Cloud云平臺提供高性能、高可用的云主機、云存儲和云網絡&#xff1b;前期通過超融合架構快速構建云基礎設施&#xff0c;來支持Jira、Redis等關鍵業務&#xff1b;并實現對原有私有云平臺業務的替代&#xff0c;按需將原有私有云業務滾動遷移到ZStack Clou…

移動端瀏覽器 jquery 獲取 pdf blob文件流 預覽pdf

最近遇到一個需求&#xff0c;一個古早的移動端 juery 項目要求做一個頁面&#xff0c;從接口獲取 pdf 文件流&#xff0c;然后預覽出來 這里使用第三方工具&#xff1a;pdf.js 代碼如下&#xff1a; // 引入相關文件<script src"../js/pdf.js" type"text…

N_1 驗證密碼

N_1 驗證密碼 題目 設計一個用戶密碼驗證程序&#xff0c;要求密碼輸入只有3次機會&#xff0c;且密碼中不能包含”*”字符。 分析 需要考慮3個問題&#xff1a;驗證次數、特殊字符和正誤密碼判斷&#xff1b;驗證次數需要使用循環&#xff0c;3個問題需要用到分支結構&…

java 系統屬性和環境屬性

Java系統屬性和環境屬性都是與Java應用程序相關的參數&#xff0c;但它們有以下區別&#xff1a; 系統屬性是由Java虛擬機&#xff08;JVM&#xff09;設置的&#xff0c;而環境屬性是由操作系統設置的。 系統屬性是以“-D”開頭的命令行參數傳遞給JVM的&#xff0c;而環境屬性…

深入理解Spring AOP的工作流程

文章目錄 引言什么是AOP&#xff1f;Spring AOP的工作原理1. JDK動態代理2. CGLIB代理 Spring AOP的注解方式Aspect注解EnableAspectJAutoProxy注解 Spring AOP的工作流程拓展應用1. 自定義注解2. 異常處理3. 切面優先級 結論 &#x1f389;深入理解Spring AOP的工作流程 ☆* o…

關于運行軟件程序出現vcruntime140.dll丟失的修復教程-解決方案

vcruntime140.dll是Microsoft Visual C庫文件的一部分&#xff0c;用于支持Windows操作系統上的應用程序。如果找不到或丟失了這個文件&#xff0c;可能會導致某些應用程序無法正常運行。下面是關于vcruntime140.dll丟失的5個修復方法&#xff0c;以及vcruntime140.dll文件屬性…