PE教程7: Export Table(引出表)

2024-02-15 11:58
文章标签 教程 table export pe 引出

本文主要是介绍PE教程7: Export Table(引出表),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

PE教程7: Export Table(引出表)

上一课我们已经学习了动态联接中关于引入表那部分知识,现在继续另外一部分,那就是引出表。

下载 范例

理论:

当PE装载器执行一个程序,它将相关DLLs都装入该进程的地址空间。然后根据主程序的引入函数信息,查找相关DLLs中的真实函数地址来修正主程序。PE装载器搜寻的是DLLs中的引出函数。

DLL/EXE要引出一个函数给其他DLL/EXE使用,有两种实现方法: 通过函数名引出或者仅仅通过序数引出。比如某个DLL要引出名为"GetSysConfig"的函数,如果它以函数名引出,那么其他DLLs/EXEs若要调用这个函数,必须通过函数名,就是GetSysConfig。另外一个办法就是通过序数引出。什么是序数呢? 序数是唯一指定DLL中某个函数的16位数字,在所指向的DLL里是独一无二的。例如在上例中,DLL可以选择通过序数引出,假设是16,那么其他DLLs/EXEs若要调用这个函数必须以该值作为GetProcAddress调用参数。这就是所谓的仅仅靠序数引出。

我们不提倡仅仅通过序数引出函数这种方法,这会带来DLL维护上的问题。一旦DLL升级/修改,程序员无法改变函数的序数,否则调用该DLL的其他程序都将无法工作。

现在我们开始学习引出结构。象引出表一样,可以通过数据目录找到引出表的位置。这儿,引出表是数据目录的第一个成员,又可称为IMAGE_EXPORT_DIRECTORY。该结构中共有11 个成员,常用的列于下表。

Field NameMeaning
nName模块的真实名称。本域是必须的,因为文件名可能会改变。这种情况下,PE装载器将使用这个内部名字。
nBase基数,加上序数就是函数地址数组的索引值了。
NumberOfFunctions模块引出的函数/符号总数。
NumberOfNames通过名字引出的函数/符号数目。该值不是模块引出的函数/符号总数,这是由上面的NumberOfFunctions给出。本域可以为0,表示模块可能仅仅通过序数引出。如果模块根本不引出任何函数/符号,那么数据目录中引出表的RVA为0。
AddressOfFunctions模块中有一个指向所有函数/符号的RVAs数组,本域就是指向该RVAs数组的RVA。简言之,模块中所有函数的RVAs都保存在一个数组里,本域就指向这个数组的首地址。
AddressOfNames类似上个域,模块中有一个指向所有函数名的RVAs数组,本域就是指向该RVAs数组的RVA。
AddressOfNameOrdinalsRVA,指向包含上述 AddressOfNames数组中相关函数之序数的16位数组。

上面也许无法让您完全理解引出表,下面的简述将助您一臂之力。

引出表的设计是为了方便PE装载器工作。首先,模块必须保存所有引出函数的地址以供PE装载器查询。模块将这些信息保存在AddressOfFunctions域指向的数组中,而数组元素数目存放在NumberOfFunctions域中。 因此,如果模块引出40个函数,则AddressOfFunctions指向的数组必定有40个元素,而NumberOfFunctions值为40。现在如果有一些函数是通过名字引出的,那么模块必定也在文件中保留了这些信息。这些 名字的RVAs存放在一数组中以供PE装载器查询。该数组由AddressOfNames指向,NumberOfNames包含名字数目。考虑一下PE装载器的工作机制,它知道函数名,并想以此获取这些函数的地址。至今为止,模块已有两个模块: 名字数组和地址数组,但两者之间还没有联系的纽带。因此我们还需要一些联系函数名及其地址的东东。PE参考指出使用到地址数组的索引作为联接,因此PE装载器在名字数组中找到匹配名字的同时,它也获取了 指向地址表中对应元素的索引。 而这些索引保存在由AddressOfNameOrdinals域指向的另一个数组(最后一个)中。由于该数组是起了联系名字和地址的作用,所以其元素数目必定和名字数组相同,比如,每个名字有且仅有一个相关地址,反过来则不一定: 每个地址可以有好几个名字来对应。因此我们给同一个地址取"别名"。为了起到连接作用,名字数组和索引数组必须并行地成对使用,譬如,索引数组的第一个元素必定含有第一个名字的索引,以此类推。

AddressOfNames AddressOfNameOrdinals
|  |
RVA of Name 1
RVA of Name 2
RVA of Name 3
RVA of Name 4
...
RVA of Name N
<-->
<-->
<-->
<-->
...
<-->
Index of Name 1
Index of Name 2
Index of Name 3
Index of Name 4
...
Index of Name N

下面举一两个例子说明问题。如果我们有了引出函数名并想以此获取地址,可以这么做:

  1. 定位到PE header。
  2. 从数据目录读取引出表的虚拟地址。
  3. 定位引出表获取名字数目(NumberOfNames)。
  4. 并行遍历AddressOfNamesAddressOfNameOrdinals指向的数组匹配名字。如果在AddressOfNames 指向的数组中找到匹配名字,从AddressOfNameOrdinals 指向的数组中提取索引值。例如,若发现匹配名字的RVA存放在AddressOfNames 数组的第77个元素,那就提取AddressOfNameOrdinals数组的第77个元素作为索引值。如果遍历完NumberOfNames 个元素,说明当前模块没有所要的名字。
  5. AddressOfNameOrdinals 数组提取的数值作为AddressOfFunctions 数组的索引。也就是说,如果值是5,就必须读取AddressOfFunctions 数组的第5个元素,此值就是所要函数的RVA。

现在我们在把注意力转向IMAGE_EXPORT_DIRECTORY 结构的nBase成员。您已经知道AddressOfFunctions 数组包含了模块中所有引出符号的地址。当PE装载器索引该数组查询函数地址时,让我们设想这样一种情况,如果程序员在.def文件中设定起始序数号为200,这意味着AddressOfFunctions 数组至少有200个元素,甚至这前面200个元素并没使用,但它们必须存在,因为PE装载器这样才能索引到正确的地址。这种方法很不好,所以又设计了nBase 域解决这个问题。如果程序员指定起始序数号为200,nBase 值也就是200。当PE装载器读取nBase域时,它知道开始200个元素并不存在,这样减掉一个nBase值后就可以正确地索引AddressOfFunctions 数组了。有了nBase,就节约了200个空元素。

注意nBase并不影响AddressOfNameOrdinals数组的值。尽管取名"AddressOfNameOrdinals",该数组实际包含的是指向AddressOfFunctions 数组的索引,而不是什么序数啦。

讨论完nBase的作用,我们继续下一个例子。
假设我们只有函数的序数,那么怎样获取函数地址呢,可以这么做:

  1. 定位到PE header。
  2. 从数据目录读取引出表的虚拟地址。
  3. 定位引出表获取nBase值。
  4. 减掉nBase值得到指向AddressOfFunctions 数组的索引。
  5. 将该值与NumberOfFunctions作比较,大于等于后者则序数无效。
  6. 通过上面的索引就可以获取AddressOfFunctions 数组中的RVA了。

可以看出,从序数获取函数地址比函数名快捷容易。不需要遍历AddressOfNamesAddressOfNameOrdinals 这两个数组。然而,综合性能必须与模块维护的简易程度作一平衡。

总之,如果想通过名字获取函数地址,需要遍历AddressOfNamesAddressOfNameOrdinals 这两个数组。如果使用函数序数,减掉nBase值后就可直接索引AddressOfFunctions 数组。

如果一函数通过名字引出,那在GetProcAddress中可以使用名字或序数。但函数仅由序数引出情况又怎样呢? 现在就来看看。
"一个函数仅由序数引出"意味着函数在AddressOfNames AddressOfNameOrdinals 数组中不存在相关项。记住两个域,NumberOfFunctionsNumberOfNames。这两个域可以清楚地显示有时某些函数没有名字的。函数数目至少等同于名字数目,没有名字的函数通过序数引出。比如,如果存在70个函数但AddressOfNames数组中只有40项,这就意味着模块中有30个函数是仅通过序数引出的。现在我们怎样找出那些仅通过序数引出的函数呢?这不容易,必须通过排除法,比如,AddressOfFunctions 的数组项在AddressOfNameOrdinals 数组中不存在相关指向,这就说明该函数RVA只通过序数引出。

示例:

本例类似上课的范例。然而,在显示IMAGE_EXPORT_DIRECTORY 结构一些成员信息的同时,也列出了引出函数的RVAs,序数和名字。注意本例没有列出仅由序数引出的函数。

.386
.model flat,stdcall
option casemap:none
include /masm32/include/windows.inc
include /masm32/include/kernel32.inc
include /masm32/include/comdlg32.inc
include /masm32/include/user32.inc
includelib /masm32/lib/user32.lib
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/comdlg32.lib

IDD_MAINDLG equ 101
IDC_EDIT equ 1000
IDM_OPEN equ 40001
IDM_EXIT equ 40003

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
ShowExportFunctions proto :DWORD
ShowTheFunctions proto :DWORD,:DWORD
AppendText proto :DWORD,:DWORD


SEH struct
   PrevLink dd ?
   CurrentHandler dd ?
   SafeOffset dd ?
   PrevEsp dd ?
   PrevEbp dd ?
SEH ends

.data
AppName db "PE tutorial no.7",0
ofn OPENFILENAME <>
FilterString db "Executable Files (*.exe, *.dll)",0,"*.exe;*.dll",0
             db "All Files",0,"*.*",0,0
FileOpenError db "Cannot open the file for reading",0
FileOpenMappingError db "Cannot open the file for memory mapping",0
FileMappingError db "Cannot map the file into memory",0
NotValidPE db "This file is not a valid PE",0
NoExportTable db "No export information in this file",0
CRLF db 0Dh,0Ah,0
ExportTable db 0Dh,0Ah,"======[ IMAGE_EXPORT_DIRECTORY ]======",0Dh,0Ah
            db "Name of the module: %s",0Dh,0Ah
            db "nBase: %lu",0Dh,0Ah
            db "NumberOfFunctions: %lu",0Dh,0Ah
            db "NumberOfNames: %lu",0Dh,0Ah
            db "AddressOfFunctions: %lX",0Dh,0Ah
            db "AddressOfNames: %lX",0Dh,0Ah
            db "AddressOfNameOrdinals: %lX",0Dh,0Ah,0
Header db "RVA Ord. Name",0Dh,0Ah
       db "----------------------------------------------",0
template db "%lX %u %s",0

.data?
buffer db 512 dup(?)
hFile dd ?
hMapping dd ?
pMapping dd ?
ValidPE dd ?

.code
start:
invoke GetModuleHandle,NULL
invoke DialogBoxParam, eax, IDD_MAINDLG,NULL,addr DlgProc, 0
invoke ExitProcess, 0

DlgProc proc hDlg:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
.if uMsg==WM_INITDIALOG
   invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETLIMITTEXT,0,0
.elseif uMsg==WM_CLOSE
   invoke EndDialog,hDlg,0
.elseif uMsg==WM_COMMAND
   .if lParam==0
     mov eax,wParam
     .if ax==IDM_OPEN
       invoke ShowExportFunctions,hDlg
     .else ; IDM_EXIT
       invoke SendMessage,hDlg,WM_CLOSE,0,0
     .endif
   .endif
.else
   mov eax,FALSE
   ret
.endif
mov eax,TRUE
ret
DlgProc endp

SEHHandler proc uses edx pExcept:DWORD, pFrame:DWORD, pContext:DWORD, pDispatch:DWORD
mov edx,pFrame
assume edx:ptr SEH
mov eax,pContext
assume eax:ptr CONTEXT
push [edx].SafeOffset
pop [eax].regEip
push [edx].PrevEsp
pop [eax].regEsp
push [edx].PrevEbp
pop [eax].regEbp
mov ValidPE, FALSE
mov eax,ExceptionContinueExecution
ret
SEHHandler endp

ShowExportFunctions proc uses edi hDlg:DWORD
LOCAL seh:SEH
mov ofn.lStructSize,SIZEOF ofn
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
   invoke CreateFile, addr buffer, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
   .if eax!=INVALID_HANDLE_VALUE
     mov hFile, eax
     invoke CreateFileMapping, hFile, NULL, PAGE_READONLY,0,0,0
     .if eax!=NULL
       mov hMapping, eax
       invoke MapViewOfFile,hMapping,FILE_MAP_READ,0,0,0
       .if eax!=NULL
         mov pMapping,eax
         assume fs:nothing
         push fs:[0]
         pop seh.PrevLink
         mov seh.CurrentHandler,offset SEHHandler
         mov seh.SafeOffset,offset FinalExit
         lea eax,seh
         mov fs:[0], eax
         mov seh.PrevEsp,esp
         mov seh.PrevEbp,ebp
         mov edi, pMapping
         assume edi:ptr IMAGE_DOS_HEADER
         .if [edi].e_magic==IMAGE_DOS_SIGNATURE
           add edi, [edi].e_lfanew
           assume edi:ptr IMAGE_NT_HEADERS
           .if [edi].Signature==IMAGE_NT_SIGNATURE
             mov ValidPE, TRUE
           .else
             mov ValidPE, FALSE
           .endif
         .else
           mov ValidPE,FALSE
         .endif
FinalExit:
         push seh.PrevLink
         pop fs:[0]
         .if ValidPE==TRUE
           invoke ShowTheFunctions, hDlg, edi
         .else
           invoke MessageBox,0, addr NotValidPE, addr AppName, MB_OK+MB_ICONERROR
         .endif
         invoke UnmapViewOfFile, pMapping
       .else
         invoke MessageBox, 0, addr FileMappingError, addr AppName, MB_OK+MB_ICONERROR
       .endif
       invoke CloseHandle,hMapping
     .else
       invoke MessageBox, 0, addr FileOpenMappingError, addr AppName, MB_OK+MB_ICONERROR
     .endif
     invoke CloseHandle, hFile
   .else
     invoke MessageBox, 0, addr FileOpenError, addr AppName, MB_OK+MB_ICONERROR
   .endif
.endif
ret
ShowExportFunctions endp

AppendText proc hDlg:DWORD,pText:DWORD
invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,pText
invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_REPLACESEL,0,addr CRLF
invoke SendDlgItemMessage,hDlg,IDC_EDIT,EM_SETSEL,-1,0
ret
AppendText endp

RVAToFileMap PROC uses edi esi edx ecx pFileMap:DWORD,RVA:DWORD
mov esi,pFileMap
assume esi:ptr IMAGE_DOS_HEADER
add esi,[esi].e_lfanew
assume esi:ptr IMAGE_NT_HEADERS
mov edi,RVA ; edi == RVA
mov edx,esi
add edx,sizeof IMAGE_NT_HEADERS
mov cx,[esi].FileHeader.NumberOfSections
movzx ecx,cx
assume edx:ptr IMAGE_SECTION_HEADER
.while ecx>0
   .if edi>=[edx].VirtualAddress
     mov eax,[edx].VirtualAddress
     add eax,[edx].SizeOfRawData
     .if edi <eax
       mov eax,[edx].VirtualAddress
       sub edi,eax
       mov eax,[edx].PointerToRawData
       add eax,edi
       add eax,pFileMap
       ret
     .endif
   .endif
   add edx,sizeof IMAGE_SECTION_HEADER
   dec ecx
.endw
assume edx:nothing
assume esi:nothing
mov eax,edi
ret
RVAToFileMap endp

ShowTheFunctions proc uses esi ecx ebx hDlg:DWORD, pNTHdr:DWORD
LOCAL temp[512]:BYTE
LOCAL NumberOfNames:DWORD
LOCAL Base:DWORD

mov edi,pNTHdr
assume edi:ptr IMAGE_NT_HEADERS
mov edi, [edi].OptionalHeader.DataDirectory.VirtualAddress
.if edi==0
  invoke MessageBox,0, addr NoExportTable,addr AppName,MB_OK+MB_ICONERROR
  ret
.endif
invoke SetDlgItemText,hDlg,IDC_EDIT,0
invoke AppendText,hDlg,addr buffer
invoke RVAToFileMap,pMapping,edi
mov edi,eax
assume edi:ptr IMAGE_EXPORT_DIRECTORY
mov eax,[edi].NumberOfFunctions
invoke RVAToFileMap, pMapping,[edi].nName
invoke wsprintf, addr temp,addr ExportTable, eax, [edi].nBase, [edi].NumberOfFunctions, [edi].NumberOfNames, [edi].AddressOfFunctions, [edi].AddressOfNames, [edi].AddressOfNameOrdinals
invoke AppendText,hDlg,addr temp
invoke AppendText,hDlg,addr Header
push [edi].NumberOfNames
pop NumberOfNames
push [edi].nBase
pop Base
invoke RVAToFileMap,pMapping,[edi].AddressOfNames
mov esi,eax
invoke RVAToFileMap,pMapping,[edi].AddressOfNameOrdinals
mov ebx,eax
invoke RVAToFileMap,pMapping,[edi].AddressOfFunctions
mov edi,eax
.while NumberOfNames>0
   invoke RVAToFileMap,pMapping,dword ptr [esi]
   mov dx,[ebx]
   movzx edx,dx
   mov ecx,edx
   shl edx,2
   add edx,edi
   add ecx,Base
   invoke wsprintf, addr temp,addr template,dword ptr [edx],ecx,eax
   invoke AppendText,hDlg,addr temp
   dec NumberOfNames
   add esi,4
   add ebx,2
.endw
ret
ShowTheFunctions endp
end start

分析:

mov edi,pNTHdr
assume edi:ptr IMAGE_NT_HEADERS
mov edi, [edi].OptionalHeader.DataDirectory.VirtualAddress
.if edi==0
  invoke MessageBox,0, addr NoExportTable,addr AppName,MB_OK+MB_ICONERROR
  ret
.endif

程序检验PE有效性后,定位到数据目录获取引出表的虚拟地址。若该虚拟地址为0,则文件不含引出符号。

mov eax,[edi].NumberOfFunctions
invoke RVAToFileMap, pMapping,[edi].nName
invoke wsprintf, addr temp,addr ExportTable, eax, [edi].nBase, [edi].NumberOfFunctions, [edi].NumberOfNames, [edi].AddressOfFunctions, [edi].AddressOfNames, [edi].AddressOfNameOrdinals
invoke AppendText,hDlg,addr temp

在编辑控件中显示IMAGE_EXPORT_DIRECTORY 结构的一些重要信息。

push [edi].NumberOfNames
pop NumberOfNames
push [edi].nBase
pop Base

由于我们要枚举所有函数名,就要知道引出表里的名字数目。nBase 在将AddressOfFunctions 数组索引转换成序数时派到用场。

invoke RVAToFileMap,pMapping,[edi].AddressOfNames
mov esi,eax
invoke RVAToFileMap,pMapping,[edi].AddressOfNameOrdinals
mov ebx,eax
invoke RVAToFileMap,pMapping,[edi].AddressOfFunctions
mov edi,eax

将三个数组的地址相应存放到esi,,ebx,edi中。准备开始访问。

.while NumberOfNames>0

直到所有名字都被处理完毕。

   invoke RVAToFileMap,pMapping,dword ptr [esi]

由于esi指向包含名字字符串RVAs的数组,所以[esi]含有当前名字的RVA,需要将它转换成虚拟地址,后面wsprintf要用的。

   mov dx,[ebx]
   movzx edx,dx
   mov ecx,edx
   add ecx,Base

ebx指向序数数组,值是字类型的。因此我们先要将其转换成双字,此时edx和ecx含有指向AddressOfFunctions 数组的索引。我们用edx作为索引值,而将ecx加上nBase得到函数的序数值。=

   shl edx,2
   add edx,edi

索引乘以4 (AddressOfFunctions 数组中每个元素都是4字节大小) 然后加上数组首地址,这样edx指向的就是所要函数的RVA了。

   invoke wsprintf, addr temp,addr template,dword ptr [edx],ecx,eax
   invoke AppendText,hDlg,addr temp

在编辑控件中显示函数的RVA, 序数, 和名字。

   dec NumberOfNames
   add esi,4
   add ebx,2
.endw

修正计数器,AddressOfNamesAddressOfNameOrdinals 两数组的当前指针,继续遍历直到所有名字全都处理完毕。

翻译:iamgufeng [Iczelion's Win32 Assembly Homepage][LuoYunBin's Win32 ASM Page]

这篇关于PE教程7: Export Table(引出表)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/711330

相关文章

Python pandas库自学超详细教程

《Pythonpandas库自学超详细教程》文章介绍了Pandas库的基本功能、安装方法及核心操作,涵盖数据导入(CSV/Excel等)、数据结构(Series、DataFrame)、数据清洗、转换... 目录一、什么是Pandas库(1)、Pandas 应用(2)、Pandas 功能(3)、数据结构二、安

2025版mysql8.0.41 winx64 手动安装详细教程

《2025版mysql8.0.41winx64手动安装详细教程》本文指导Windows系统下MySQL安装配置,包含解压、设置环境变量、my.ini配置、初始化密码获取、服务安装与手动启动等步骤,... 目录一、下载安装包二、配置环境变量三、安装配置四、启动 mysql 服务,修改密码一、下载安装包安装地

MySQL CTE (Common Table Expressions)示例全解析

《MySQLCTE(CommonTableExpressions)示例全解析》MySQL8.0引入CTE,支持递归查询,可创建临时命名结果集,提升复杂查询的可读性与维护性,适用于层次结构数据处... 目录基本语法CTE 主要特点非递归 CTE简单 CTE 示例多 CTE 示例递归 CTE基本递归 CTE 结

电脑提示d3dx11_43.dll缺失怎么办? DLL文件丢失的多种修复教程

《电脑提示d3dx11_43.dll缺失怎么办?DLL文件丢失的多种修复教程》在使用电脑玩游戏或运行某些图形处理软件时,有时会遇到系统提示“d3dx11_43.dll缺失”的错误,下面我们就来分享超... 在计算机使用过程中,我们可能会遇到一些错误提示,其中之一就是缺失某个dll文件。其中,d3dx11_4

Linux下在线安装启动VNC教程

《Linux下在线安装启动VNC教程》本文指导在CentOS7上在线安装VNC,包含安装、配置密码、启动/停止、清理重启步骤及注意事项,强调需安装VNC桌面以避免黑屏,并解决端口冲突和目录权限问题... 目录描述安装VNC安装 VNC 桌面可能遇到的问题总结描js述linux中的VNC就类似于Window

Go语言编译环境设置教程

《Go语言编译环境设置教程》Go语言支持高并发(goroutine)、自动垃圾回收,编译为跨平台二进制文件,云原生兼容且社区活跃,开发便捷,内置测试与vet工具辅助检测错误,依赖模块化管理,提升开发效... 目录Go语言优势下载 Go  配置编译环境配置 GOPROXYIDE 设置(VS Code)一些基本

MySQL 8 中的一个强大功能 JSON_TABLE示例详解

《MySQL8中的一个强大功能JSON_TABLE示例详解》JSON_TABLE是MySQL8中引入的一个强大功能,它允许用户将JSON数据转换为关系表格式,从而可以更方便地在SQL查询中处理J... 目录基本语法示例示例查询解释应用场景不适用场景1. ‌jsON 数据结构过于复杂或动态变化‌2. ‌性能要

解决1093 - You can‘t specify target table报错问题及原因分析

《解决1093-Youcan‘tspecifytargettable报错问题及原因分析》MySQL1093错误因UPDATE/DELETE语句的FROM子句直接引用目标表或嵌套子查询导致,... 目录报js错原因分析具体原因解决办法方法一:使用临时表方法二:使用JOIN方法三:使用EXISTS示例总结报错原

Windows环境下解决Matplotlib中文字体显示问题的详细教程

《Windows环境下解决Matplotlib中文字体显示问题的详细教程》本文详细介绍了在Windows下解决Matplotlib中文显示问题的方法,包括安装字体、更新缓存、配置文件设置及编码調整,并... 目录引言问题分析解决方案详解1. 检查系统已安装字体2. 手动添加中文字体(以SimHei为例)步骤

Java JDK1.8 安装和环境配置教程详解

《JavaJDK1.8安装和环境配置教程详解》文章简要介绍了JDK1.8的安装流程,包括官网下载对应系统版本、安装时选择非系统盘路径、配置JAVA_HOME、CLASSPATH和Path环境变量,... 目录1.下载JDK2.安装JDK3.配置环境变量4.检验JDK官网下载地址:Java Downloads