转自http://www.woodmann.com/fravia/iceman1.htm

WIN32 - Inside Debug API
------------------------
(Things you need to know: the mysterious CONTEXT structure)

by Iceman, 20 March 1998

I'm very proud that my previous work "Tweaking with memory in Windows 95" was good
enough to open a new section ,"+HCU's PAPERS", at Reverser's.That's very stimulating ,so I'm
back! I hope that will be many other contributors to this section.Let's bring light in the
shadows!
BTW, Reverser ,I really like the picture for your new section.I'm with you boys,now
and forever!And one more thing:for  now one I will send you my essays in .htm format.No more
plain text files!
In this document I will focus on Debug API functions.I think that it is an
interesting chapter,who worth a closer lock.
Note:In order to use those functions in Windows NT your user must have debug
privileges access right granted.I don't know for sure but it seems that NT does not grant
this for default to administrators.
I have started to work on part two of "Tweaking with memory in Windows 95" this
time I want to present a VxD aprroach.It's nice to write self-modifying code using the linker
trick(Make code section read-write at link time).But wouldn't be nicer to relay only on VxD calls
to tweak with memory leaving that damned section write protected and without ANY high-level
calls to functions like VirtualProtect? The target will be this time the virtual memory manger
itself.Anyone out there who could HELP?.

The document has the following structure:

Chapter1:Functions and structures.
Chapter2:Debug events.
Chapter3:Creating or attaching a process for being debugged.
Chapter4:The main loop: WaitForDebugEvent - ContinueDebugEvent.
Chapter5.Handling debug events.
Chapter6:GetThreadContext & Set ThreadContext(Advanced stuff)
6.1 Thread Contexts explained
6.2 Injecting code in another process.
Chapter7:Notes

===============================================================================================

Chapter1:Functions and structures
----------------------------------

A good WIN32 API reference for future reference is OK.I don't explain here all
the parameters those functions take, nor all structures.But for now,let's see the API:

ContinueDebugEvent
DebugActiveProcess
DebugBreak
FatalExit
FlushInstructionCache
GetThreadContext
GetThreadSelectorEntry
IsDebuggerPresent
OutputDebugString
ReadProcessMemory
IsDebuggerPresent
SetThreadContext
WaitForDebugEvent
WriteProcessMemory

As we can see , two old friends:WriteProcessMemory & ReadProcessMemory.We all know
them very well,so let's go further.
FlushInstructionCache ,IsDebuggerPresent,OutputDebugString? Self-explanatory.

DebugActiveProcess
------------------

This function allows a debugger to attach to an active process.

BOOL DebugActiveProcess(
DWORD dwProcessId  
   );
Parameters:

DWORD dwProcessId: PID of process to attach

WaitForDebugEvent
-----------------

This function allow the debugger to wait until a debug event heapens
in target process.

BOOL WaitForDebugEvent(
       LPDEBUG_EVENT lpDebugEvent,
       DWORD dwMilliseconds
          );
Parameters:
  LPDEBUG_EVENT lpDebugEvent: Pointer to a  DEBUG_EVENT type structure.
      This struct will receive info about the
      debug event trapped.
  DWORD dwMilliseconds:       Number of ms. to wait.Could be INFINITE.
        If it is INFINITE WaitForDebugEvent does
      not return until a debug event occurs.
ContinueDebugEvent
------------------

This function allow the debugger to resume a thread that previously raised a
debug event.

BOOL ContinueDebugEvent(
DWORD dwProcessId,
              DWORD dwThreadId,
         DWORD dwContinueStatus
  );
Parameters:
  
DWORD dwProcessId:       PID of process beeing debugged
            DWORD dwThreadId:        TID of thread to be resumed
         DWORD dwContinueStatus : DWORD that specify how the thread will
  continue.Two values defined:
  DBG_CONTINUE  & DBG_EXCEPTION_NOT_HANDLED.

Debug Break
----------
        This function causes a breakpoint in current process.

VOID DebugBreak(VOID);

FatalExit
---------
This function force the exit of caller process,transferring execution to debugger

VOID FatalExit(
      int ExitCode
      );
Parameters:
  int ExitCode : Exit code

GetThreadContext & Set Thread context
-------------------------------------
Those functions are used to retrieve & set the context of a thread.See chapter 6.

BOOL GetThreadContext(
      HANDLE hThread,
              LPCONTEXT lpContext
      );

BOOL SetThreadContext(   
      HANDLE hThread,
      CONST CONTEXT *lpContext
      );
Parameters:
HANDLE hThread:      A handle to thread whose context is being read
     or set.
LPCONTEXT lpContext: Pointer to a CONTEXT structure to receive /set
     context info.

GetThreadSelectorEntry
----------------------
The GetThreadSelectorEntry function retrieves a descriptor table entry for
the specified selector and thread.

BOOL GetThreadSelectorEntry(
    HANDLE hThread,
    DWORD dwSelector,
    LPLDT_ENTRY lpSelectorEntry
    );
Parameters:
HANDLE hThread:       A handle to thread containing specified
      selector.
        DWORD dwSelector:       Selector Number
                LPLDT_ENTRY lpSelectorEntry   Pointer to a structure that receive
      descriptor table.

================================================================================================

Chapter2:Debug events.
----------------------

From Debug API functions point of view a debug events is an object used to communicate
with the debugger.When a debug event occurs in target process the OS inform the debugger about
this.The debugger use WaitForDebugEvent to retrieve info about the event that occurred in target
process(See chapter 5).Following debug events exists:
1.CREATE_PROCESS_DEBUG_EVENT & EXIT_PROCESS_DEBUG_EVENT raised every time than a new
process is created/destroyd by the process being debugged.
2.CREATE_THREAD_DEBUG_EVENT & EXIT_CREATE_THREAD_DEBUG_EVENT raised whenever a new
thread object is created/destroyed by the process being debugged.
3.LOAD_DLL_DEBUG_EVENT & UNLOAD_DLL_DEBUG_EVENT generated whenever the target loads/
unloads a dll.
4.OUTPUT_DEBUG_STRING_EVENT generated than target calls OutputDebugString.
5.EXCEPTION_DEBUG_EVENT generated when an exception occurs in target process.This
include breakpoint instructions such INT 3 , DivideOverflow .....
6.RIP_DEBUG_EVENT generated when a RIP exception occurs.
WaitForDebugEvent receives the debug event and returns information about the event
in a DEBUG_EVENT structure.This structure is defined as below in WIN32 AP:

typedef struct _DEBUG_EVENT { 
    DWORD dwDebugEventCode;
    DWORD dwProcessId;
    DWORD dwThreadId;
    union {
          EXCEPTION_DEBUG_INFO Exception;
          CREATE_THREAD_DEBUG_INFO CreateThread;
          CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
          EXIT_THREAD_DEBUG_INFO ExitThread;
          EXIT_PROCESS_DEBUG_INFO ExitProcess;
          LOAD_DLL_DEBUG_INFO LoadDll;
          UNLOAD_DLL_DEBUG_INFO UnloadDll;
          OUTPUT_DEBUG_STRING_INFO DebugString;        
  RIP_INFO RipInfo;
      } u;
      } DEBUG_EVENT;
The member dwDebugEventCode contains a value indicating which kind of debug  events
was ocureed.The dwProcessId member contain the PID of process in which the debug event occurred.
The union u member is a classic C/C++ union.It is reflected in a structure whose type is
determined by DWORD dwDebugEventCode.This structure contains extended information about the
event that ocurred.I don't list all of them here because it's pointless.
Also note that a CREATE_PROCESS_DEBUG_EVENT is generated than a debugger attach to a
target process.

================================================================================================

Chapter3:Creating or attaching a process for being debugged
------------------------------------------------------------

In this short chapter I present you how to create a process for being debugged,or
how to attach to an already running process.

3.1:Creating a process for being debugged
Use CreateProcess to create the process being debugged.Call this function
   with dwCreationFlags parameter with one of following values DEBUG_PROCESS or
   DEBUG_ONLY_THIS_PROCESS.If target process is created with DEBUG_PROCESS creation
   flag than the debugger will receive events from all processes crated by target
   process.If dwCreationFlags=DEBUG_ONLY_THIS_PROCESS than the debugger will receive
   debug events only from target process ignoring child processes.As usually you
   can use PROCESS_INFORMATION structure to ret rive handles to both the created
   process and it's primary thread as well as the PID an TID(for primary thread).

3.2:Attaching to an already running process
Use DebugActiveProcess function.If this function returns successfully you
    are attached to target as if you called CreateProcess with DEBUG_ONLY_THIS_PROCESS
    flag.

Note that in WindowsNT DebugActiveProcess can easily fail if we try to attach to a
process that was created with a security descriptor that denies requested access.In WIN95
the only thing you have to worry is to pas a valid PID to DebugActiveProcess.That's it man!
NT has better security.
Attaching to a process is an elegant method but sometimes the loader method is the
only solution.It's up to you what method to use.For a simple game trainer it's OK to attach
but if you really want to do cool things...,better use the loader method.It gives you full
control over the target process and it's threads.

================================================================================================

Chapter4:The main loop: WaitForDebugEvent - ContinueDebugEvent
--------------------------------------------------------------

A minimum skeleton for using Debug API function is easy to implement.All you have to
do is to create a process for being debugged and the implement code to watch for debug events.
I call the part responsible with watching debug events "The Main Loop".Why?Because is very
simple to implement as a While loop.The functions you have to use for this are
WaitForDebugEvent - ContinueDebugEvent. As we have seen before WaitForDebugEvent waits for a
certain amount of time for a debug event to occur in target process.If a debug event does not
occur in this time the function times-out and return FALSE. If a debug events occurs than
this function return TRUE,fill a  DEBUG_EVENT type structure with info about event type and
freeze the thread in witch the debug event ocurred.The programmer is responsible to perform
event type checking and take appropriate meassures.After the specific code for handling debug
event is executed we have to use ContinueDebugEvent to resume thread execution and wait for
other events to occure.Another thing to worry: the only thread witch is allowed to call
WaitForDebugEvent is the thread who created or attached to target process.So let's see some
code:

PROCESS_INFORMATION pi;
STARTUP_INFO        si;
DEBUG_EVENT     devent;
if(CreateProcess( 0 , "target.exe" , 0 , 0 ,FALSE ,DEBUG_ONLY_THIS_PROCESS , 0 ,0 ,
&si , &pi))
while(TRUE)
  {
{
if (WaitForDebugEvent( &devent , 150)) // wait 150 ms for debug event
{
switch(devent.dwDebugEventCode)
      {
       case CREATE_PROCESS_DEBUG_EVENT:
// your handler here
       break;
       case EXIT_PROCESS_DEBUG_EVENT:
// your handler here
break;
       case EXCEPTION_DEBUG_EVENT:
// your handler here
break;

}
ContinueDebugEvent(devent.dwProcessId , devent.dwThreadId , DBG_CONTINUE);

}

else
{
         // other operations
}

}
  }  // while end here
else
{
MessageBox(0,"Unexpected load error","Fatal Error" ,MB_OK);
}

=================================================================================================

Chapter5.Handling debug events
-------------------------------

In previous example we can see that how we can trap debug events and take appropriate
actions using case/switch C /C++ statements.Each debug event has a personal handler who
gets executed when corresponding debug event occurs.More information about the debug event
can be found in union u member of DEBUG_EVENT.As a example let's the structure corresponding
to EXCEPTION_DEBUG_EVENT.I choose this because encountering breakpoints and tracing through
code generates an exception debug event.See a API reference for other events.

typedef struct _EXCEPTION_DEBUG_INFO
   {
    EXCEPTION_RECORD ExceptionRecord;
    DWORD dwFirstChance;
           } EXCEPTION_DEBUG_INFO;

In this case we have to retrieve data we need from another structure,member of
EXCEPTION_DEBUG_INFO structure.This is EXCEPTION_RECORD structure in which , finally ,
we can find all data we need about trapped exception.Let's see:

typedef struct _EXCEPTION_RECORD
{
        DWORD ExceptionCode;
                        DWORD ExceptionFlags;
                        struct _EXCEPTION_RECORD *ExceptionRecord;
                        PVOID ExceptionAddress;
                        DWORD NumberParameters;
                        DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
                        } EXCEPTION_RECORD;

DWORD ExceptionCode:    Specifyes the type of exception
ExceptionFlags     :    0 if exception is a continuable exception
        EXCEPTION_NONCONTINUABLE if exception is not continuable.
ExceptionRecord    :    Pointer to  an associated EXCEPTION_RECORD structure
        PVOID ExceptionAddress: Pointer to the address where exception occurred
DWORD NumberParameters: Number of parameters defined in ExceptionInformation
ExceptionInformation:   Additional 32 bit array.For most exception is undefined.

Using the information from those structures we can find all we need.We can retrieve
the thread there exception occurred , type of exception , if we can continue execution or not,
the address where exception occurred  and others.
Note that trying to continue a EXCEPTION_NONCONTINUABLE exception type will
generate a EXCEPTION_NONCONTINUABLE_EXCEPTION exception.
Currently used exceptions are EXCEPTION_BREAKPOINT and EXCEPTION_SINGLE_STEP.The
first exception is raised on a breakpoint hit, the seconds signalizes that trace trap
signals that one instruction has been executed.
Using a similar mechanism you can gather information about threads , dll's used
by running process and other things.

===============================================================================================

Chapter6:GetThreadContext & Set ThreadContext(Advanced stuff)
-------------------------------------------------------------

6.1 Thread Contexts explained
-----------------------------

I really enjoyed writing this chapter.All others are things easy to figure out.Don't
scare it not really difficult to understand what's going on in this chapter.Before to present
those two functions and their use I want to remember some basic things about processes and
threads.
In WIN32 philosophy a process is a object who has an private address space , code ,
data , and a primary thread.Each process has at the very beginning only one thread.From the
primary thread we can later create other threads which run in the same address space.Contrary
to the popularly belief a process does NOT execute any kind of code.The threads are the objects
who executes the code.The thread objects share the same address space and resources but they
have individual contexts.What means that? Windows95 and WindowsNT are multitasking AND
multithread operating systems.The OS seems to run all threads in the same time , but this is
not true. Every individual thread is scheduled for execution for a short time , and the the
OS save the thread state in a structure called CONTEXT structure and goes for the next thread.
The information saved in this structure represents the thread context and is formed by:
- threads machine registers (CPU registers)
- the kernel stack and the user stack address
- thread environment block address.
The the OS encounter again our thread it restores it's context info from associated
structures and resume execution like nothing happened.
Ok,so let's see the CONTEXT structure.Unfortunately seems that Microsoft does not
include info about this structure API help files. The structure is documented at minimum
in winnt.h header file in Watcom compilers(can be elsewhere in others.Keep looking).Keep in
mind that this structure is hardware dependent so expect different implementations for
x86 , Alpha...

typedef struct _CONTEXT {
    DWORD ContextFlags;
    DWORD   Dr0;
    DWORD   Dr1;
    DWORD   Dr2;
    DWORD   Dr3;
    DWORD   Dr6;
    DWORD   Dr7;
    FLOATING_SAVE_AREA FloatSave;
    DWORD   SegGs;
    DWORD   SegFs;
    DWORD   SegEs;
    DWORD   SegDs;
    DWORD   Edi;
    DWORD   Esi;
    DWORD   Ebx;
    DWORD   Edx;
    DWORD   Ecx;
    DWORD   Eax;
    DWORD   Ebp;
    DWORD   Eip;
    DWORD   SegCs;             
    DWORD   EFlags;            
    DWORD   Esp;
    DWORD   SegSs;

} CONTEXT;

typedef struct _FLOATING_SAVE_AREA {
    DWORD   ControlWord;
    DWORD   StatusWord;
    DWORD   TagWord;
    DWORD   ErrorOffset;
    DWORD   ErrorSelector;
    DWORD   DataOffset;
    DWORD   DataSelector;
    BYTE    RegisterArea[SIZE_OF_80387_REGISTERS];
    DWORD   Cr0NpxState;
} FLOATING_SAVE_AREA;

typedef FLOATING_SAVE_AREA *PFLOATING_SAVE_AREA;

DWORD ContextFlags: the folowing values are defined:

CONTEXT_CONTROL          // SS:SP, CS:IP, FLAGS, BP
CONTEXT_INTEGER          // AX, BX, CX, DX, SI, DI
CONTEXT_SEGMENTS         // DS, ES, FS, GS
CONTEXT_FLOATING_POINT   // 387 state
CONTEXT_DEBUG_REGISTERS  // DB 0-3,6,7

CONTEXT_FULL=(CONTEXT_CONTROL | CONTEXT_INTEGER > CONTEXT_SEGMENTS)

Watch out CONTEXT_FULL does not include CONTEXT_DEBUG_REGISTERS and
CONTEXT_FLOATING_POINT.You must specify them independently.It's huge and ugly, isn't it?
Ok we know now how a CONTEX structure is looking.Now let's see what can we do with
this monster.First let's talk a little about GetThreadContext & SetThreadContext.
The function GetThreadContext is used to get a thread context.
BOOL GetThreadContext(
   HANDLE hThread,
   LPCONTEXT lpContext
        );
  hThread is the handle of thread whose context is to be retrieved.
LPCONTEXT lpContext is a pointer to a CONTEXT structure.

PRIOR TO USE GetThreadContext you MUST initialize  ContextFlags member with
the appropriate flag.Use This to set the amount of info to retrieve.For example if
you specify CONTEXT_CONTROL value  for ContextFlags only SS:SP, CS:IP, FLAGS, BP
will be saved.
The function SetThreadContext is used to set the thread context.
BOOL SetThreadContext(
HANDLE hThread,
         CONST CONTEXT *lpContext
     );
Like before hThread is a handle to destination thread and  CONST CONTEXT
*lpContext is a pointer to a CONTEXT structure.The amount of information restored
is determined by ContextFlags member.
You may want to consider another thing.NEVER try to set a thread context
while the thread is running.I consider this "one way ticket to the hell".Use
SuspendThread to stop a running thread.Later after you set the context you
can use ResumeThread function.Warning: using ResumeThread does not guarantee
that the target thread will indeed resume executions.Why?Every thread have an
thread suspend count.When the thread is running the counter is 0.Every time when
we use SuspendThread this counter is incremented by one.So we call SuspendThread.
The counter will be updated to 1.But W_95 and W_NT are multithread enviroments
so another thread can call too SuspendThread on our thread.Now the counter
is 2.Calling ResumeThread once will have only one effect.The counter is again 1.
Thread execution is not resumed until the thread suspend count is 0.(ResumeThread function
decrements the suspend counter ).So how can we be sure that the thread resumed execution?
Simple.Examine the return value. If it's 0 then the thread  was not suspended.If it's
1 the thread was suspended but resumed execution.If it's greater than 1 the thread suspend
counter was decremented but the thread was not resumed.A value of 0xffffffff means that
ResumeThread failed.

6.2 Injecting code in another process.
--------------------------------------

Now let's take a deep breath.We are almost through.As usually I want to present
you an interesting trick.Let's inject some code into another process address space.We know
how,but first let's talk about a little impediment.We need some committed memory to store
our brand new code.A VirtualAllocEx function was not provided in WIN95 API.It seems that
this one along with it's companion VirtualFreeEx exists under NT.
If our code is very little we can use the space provided by our compilers: The MS-DOS stub of
the PE files,copyright strings or even unnecessary data strings(I dont care too much if in Help
About the program says that it was developed by "!^$##@*^$f76").Another method is to
save a code page of target process , overwrite with new code , execute new code , restore
code page.Let's see this step by step.

1. Use CreateProcess to create a process for being debugged.
2. Build the "Main Loop" WaitForDebugEvent - ContinueDebugEvent
3. Stop the target thread. Use SuspendThread.
4. Use VirtualProtectEx to set a read-write permission to target page
5. Use ReadProcessMemory to save the target page.
6. Use GetThreadContext to save the thread context.
7. Use WriteProcessMemory to write new code page.
8  Make sure that the last instruction in the new code is a INT 3.We need this to
   take control when our code finished.The INT 3 will be trapped by our little
   debugger-like application EXCEPTION_DEBUG_EVENT.Make sure that is a
   EXCEPTION_BREAKPOINT  and has occurred at the address there our INT 3 resides.
9. Make a temporary copy of saved CONTEXT structure.
       10. Set the new eip in the temporary CONTEXT structure (You now what is eip , didn't
   you?
       11.Resume execution of the thread.Watch it executing our new code.When INT 3 gets
  executed our little loader will trap it.The target thread is stopped.
       12.Restore the original code page using WriteProcessMemory.
       13.Restore the protection attributes on target page.
       14.Use SetThreadContext to set thread context from the first CONTEXT structure.
       15.Resume thread.

If we need that our resides in target process address space at the same time with the
original code,and our code is BIG we have to commit some memory in the target process address
space.The code to call VirtuallAlloc is very small,so use previous method to call VirtualAlloc
in the context of the target process.This will commit memory in target's address space and
return a pointer to it.Several kb should be more than enough,so don't be a fool and start
to commit cosmic values like 10 Mb.I wonder if there is another method to implement a
VirtualAllocEx under WIN95.I keep looking.Anyone now if VirtualAllocEx is implemented
in Memphis(future Windows 98)?.
      If you ever need to convert a segment relative address in linear virtual address you
can use GetThreadSelectorEntry.
      Final words: !!WATCH!! the stack.DON'T mess IT.If you do , you will be sorry.

================================================================================================

iceman写的关于Debug API的文章,特别是关于CONTEXT结构的描述,有空再翻译(转贴)相关推荐

  1. 智能AI文章写作伪原创-免费API接口

    wyc-api 智能AI文章写作伪原创接口API 更新时间: 2022/10/08 接口介绍 支持6种伪原创算法 不是单单关键词替换,而是语句组合替换,并在不断更新数据. 支持火车头插件使用 在线体验 ...

  2. win32 debug api 原理

    在Win32中自带了一些API函数,它们提供了相当于一般调试器的大多数功能,这些函数统称为Win32调试API(Win32 Debug API).利用这些API可以做到加载一个程序或捆绑到一个正在运行 ...

  3. 利用Win32 Debug API打造自己的调试器Debugger

    很多朋友都梦想有自己的Debugger程序,今天我们就来自己制作一个.作为一个Debugger程序,其最基本的功能框架其实就是完成2件事情:  启动目标程序.  实时监控目标程序的运行,并做出相应 ...

  4. 写一篇好的技术文章有多难?

    就我而言,一年里我也没写出几篇让自己满意的文章.因为写一篇好的技术文章真的很难. 对于一篇好的文章来说,它有这么一些要求: 构建文章所需的理论体系 实践及代码验证 公正又有所偏爱的观点 又要注意这么一 ...

  5. 零基础怎么写好一篇博客文章

    大家好,我是csdn的博主:lqj_本人 现在是一名大二的学生. 我很荣幸被CSDN选为2023年的新星计划.小程序开发赛道的导师. 本人简介: 本人是2022年下半年开始进入CSDN开发者社区进行博 ...

  6. 分析手机网站的优势思维结构图_写了100多篇原创文章,我常用的在线工具网站推荐给大家...

    摘要 不知不觉写博客已经一年多了,累计写了100多篇原创文章,今天给大家分享下我经常使用的在线工具网站,希望对大家有所帮助! Markdown Nice 支持自定义样式的在线Markdown编辑器,编 ...

  7. 十三、写了两年多Python文章的我,带你走进Python数据分析

    @Author : By Runsen @Date : 2020/5/13 作者介绍:Runsen目前大三下学期,专业化学工程与工艺,大学沉迷日语,Python, Java和一系列数据分析软件.导致翘 ...

  8. 网络拓扑图画图工具_写了100多篇原创文章,我常用的在线工具网站推荐给大家!...

    不知不觉写博客已经一年多了,累计写了100多篇原创文章,今天给大家分享下我经常使用的在线工具网站,希望对大家有所帮助! Markdown Nice 支持自定义样式的在线Markdown编辑器,编辑完成 ...

  9. 如何写好一篇伪原创文章

    从这次建站开始,我就没想过去做伪原创或者采集,可是最近,有好些站长在求友链的时候顺带问这个问题,有几位我是这么说的:一.自己写的,没有参考或直接复制别人内容:二.让搜索引擎看起像原创,或者直接就是原创 ...

最新文章

  1. qt 使用非系统字库
  2. 有了Windows Defender应用程序防护功能,再也不担心电脑免遭恶意***
  3. python多线程和多进程——python并行编程实验
  4. CONVERT_YEAR_WITH_THRESHOLD
  5. ITK:Mersenne Twister随机数生成器
  6. 执行全文索引时出现权限不足的解决方法
  7. python爬虫分布式怎么构造_如何构建一个分布式爬虫:实战篇
  8. Mac 解决 command not found: mysql
  9. 电脑处理器排行榜2021版
  10. HTML学习总结(4)——表格/块/内联元素/iframe/颜色/脚本/实体
  11. 大数据之Superset
  12. ssa/ass字幕格式全解析
  13. IPv6与IPv4的区别 网信办等三部推进IPv6规模部署
  14. 官方太空射击游戏总结
  15. 374C. Inna and Dima
  16. 周边pd是什么意思_韩国综艺里经常说的VJ、PD是什么意思
  17. ALPS TCP新建配置——网络测试仪实操
  18. poj解题报告——2325
  19. 什么是三相交流电源对称?对称三相交流电源特征
  20. <动手学深度学习>之pytorch版本,配置d2lzh_pytorch包

热门文章

  1. 信奥日记——动态规划(动规初步)
  2. 上传APP到AppStore遇到的各种错误
  3. VScode 显示垂直标尺
  4. 超详细的抖音运营全攻略
  5. 混合牛奶(春季每日一题 9)
  6. 10.11_attention
  7. 计算机键盘fn,USB键盘Fn功能键调节方法
  8. excel自动化的第一个实用例子(宿舍分饭)
  9. 基金小白理财收益超过1万+的回顾
  10. log 的抓取与分析