Basic debugging & tips using NTSD

【AI智能体】Langchain 上下文记忆使用实战操作详解 阅读详情

http://www.opferman.net/Text/ntsd.txt

Basic debugging & tips using NTSD

By Toby Opferman (AKA Scrat/_Secret)
http://www.opferman.com
http;//www.opferman.net
toby@opferman.com


NOTE:  This tutorial is without warrenty.  Use at your own risk. I am
not in any way responsible for any damages caused by use of this tutorial.



	NTSD is a debugger for WinNT & Win2K that can be used to debug
application bugs and traps.  To set NTSD to be launched when an application
traps, you need to set the following registry key:

HKEY_LOCAL_MACHINE/Software/Microsoft/WindowsNT/CurrentVersion/AeDebugger

Set the value "Debugger" to
"ntsd -p %ld -e %ld -g"
or just "ntsd -p %ld -e %ld"  
or just "ntsd -p %ld"

You can do ntsd /?  and check out the options you like.  The -g does is when
it traps to the debugger, it does "auto go".


Set the value "Auto" to
1

This is if you want to change to NTSD.  If you have devstudio installed,
this value may be set to devstudio, or it could be set to Dr. Watson.
If it is set to Dr. Watson, I would suggest setting it to NTSD instead,
since Dr. Watson just gives a stack trace and a small assembly listing.


	When an application traps, if the trap is not handled, NTSD should
catch and break into the process where it trapped.  To start using NTSD on
an application, when it is set as your default debugger you can right click
on the process in task manager and click "Debug Process".  You can also
get the PID of the process in Task Manager or by running qprocess and 
doing NTSD -P <PID>.


	Some of the basic commands that you would want to know are:
kb - Stack Dump
dd - Dump Dwords
da - Dump ANSI String
du - Dump UNICODE String
~* - List Threads
~<number><command> - Perform <command> on Thread <number> 
                     (Such as ~1kb Stack Dump Thread 1)
u - Unassemble
!reload - Reload Symbols
g - Go
p - Step 1 instruction, but do not trace into function calls
t - Trace into, and step 1 instruction
bp - Set Breakpoint
bc - Clear Breakpoint

	Before we start on NTSD, let us look at symbols.  Now, when using
a debug build, symbols along with other debug information are embedded
into the executable and the assembly is not optimized.  This makes it
easier to debug.

	However, when you want to release you would like to build a retail
build.  Synchronization, timing and memory may show up more in a retail
build and you would have a hard time debugging them.  What we want to be
able to do, is debug our applications without recompiling and throwing
in 100 print statements.  On that note, if this bug is not that reproducable
or the steps to reproduce it are long and not very easy, we would like to
be able to find out what the problem is from it's crashed state.  

Let's take a look at something.  I have a small source file that I wrote
when I was working on an OS to remove the binary headers from executables.

Compiling with optimizations and linking this source like so:

cl /Ox /c rmhdr.c
link /SUBSYSTEM:CONSOLE /OUT:rmhdr.exe rmhdr.obj 

Produces:
RMHDR    EXE        32,768

Now, there are no symbols or memory mappings of what the binary contains.
So, if we compile with debug information, all we really need are the symbols.

cl /Ox /Zi /c rmhdr.c
link /SUBSYSTEM:CONSOLE /OUT:rmhdr.exe rmhdr.obj -pdb:rmhdr.pdb -debugtype:both -debug

Produces:
RMHDR    EXE        53,506

53k Executable.  Now, this file can be used to debug directly and you would have
symbols for the function calls which make it easier to debug.   However, Would
you want to release a file with debug information?  On the other hand, if you
release a file without debug information and it crashes on someone's machine
and you want to be able to debug this machine easily, you would want the debug
information.  So, what should we do?


rebase -b 0x00400000 -x ./ -a rmhdr.exe

When we run the rebase utility, we now have 2 files.
RMHDR    DBG        16,876  12-13-00  8:53p rmhdr.dbg
RMHDR    EXE        37,136  12-13-00  8:53p rmhdr.exe

37k is a little more than 32k, but you're already programming in C and
not assembly (Or C++ if that's the case)  so you're not too worried about
size that much, at least not to the point to complain about this or else
you'd be writing in assembly.

Now, you say you have this DBG file, but what can you do with it?  
There's a few things you can do with it, let me provide an example.

I purposely put a bug into this C program and will run it on Windows 98.

And now you get the "This program has performed an illegal operation and will be
shut down"  "If this problem persists, contact the program vendor"

It does ask you "Close", "Debug" or "Details"  Well, you can debug, (The best
debugger for Win98 would be like softice or even VC++), but I think that
we can get enough information just by hitting "Details".

This is what we get when we hit details:

RMHDR caused an invalid page fault in
module RMHDR.EXE at 0177:0040124b.
Registers:
EAX=3f3e3d3c CS=0177 EIP=0040124b EFLGS=00010246
EBX=00530000 SS=017f ESP=0063fcd4 EBP=0063fde4
ECX=004083c8 DS=017f ESI=81756014 FS=23f7
EDX=00000000 ES=017f EDI=00000000 GS=0000
Bytes at CS:EIP:
c7 00 0a 00 00 00 8b 8d f8 fe ff ff 83 c1 04 89 
Stack dump:
004083c8 004083e8 3f3e3d3c 00000000 47464544 
4b4a4948 0063fd20 816e8050 816edcdc cddb6b80 
0063fd20 bff7a0fe bff7b317 816e8000 00000000 
00000014 


Notice EIP = 0040124b, just using that number, we can narrow it down.

There is a utility that comes with VC++ called dumpbin, which is very useful.

dumpbin /symbols rmhdr.dbg > t.txt


We now have a mapping of all the symbols and their addresses.

We have to map 0040124b to the image.  We usually will just take
124b part, or will have to adjust the number to match the symbols (i.e.,
it could really be 10124b in the map, depends on where the EXE was loaded
into memory).  One way is to go into t.txt now, and look at the very last
entry.

235 00009F04 SECT3  notype       External     | __acmdln
236 0000A000 DEBUG  notype       External     | end

    0040124b
Are the very last entries.  Comparing the values, you can see that
0040124b is not an absolute 1-1 mapping to the symbols.  Notice also that
this is simple since we did not have to look at the stack and we know that
EIP is still inside RMHDR.EXE (I made sure of that when I put the bug in).
There are times when you may have to map the entire stack to find out the calling 
path.  

Well, let's try 124b.  Now remeber, we can't search on this number.  This number
will not exist in the mapping.  We need to find 2 functions where this number
would lie between.

0F7 0000114F SECT1  notype ()    External     | _RemoveHdr
0F8 00001295 SECT1  notype ()    External     | _atol

And here we found it.  124B is between _RemoveHdr and _atol in the source.
This means, that the function that crashed is _RemoveHdr.  

Now, if we would have dropped into a debugger, we could look at the stack
and even find out who called RemoveHdr, what the values on the parameters are,
and etc.  However, we didn't.  I also ran this in Win98. We could generate
a MAP file or COD file that would show the Asm with C with addresses and
we'd be able now to go directly to RemoveHdr and find the spot where it
crashed. (And I bet you never knew how useful those little GPF messages
were!)

However, this tutorial isn't just about debugging.  It's about
debugging with NTSD.

So, let's take it to Win2k with NTSD set to trap as our default debugger. 
(Remeber, we set our default debugger as NTSD eariler).

Now, I run the program and it traps into NTSD.

Microsoft(R) Windows 2000 Debugger
Version 5.00.2184.1
Copyright (C) Microsoft Corp. 1981-1999

CommandLine:
*** wait for debug event
Symbol search path is: C:/WINDOWS
NTSD ModLoad: 00400000 0040a000   rmhdr.exe
NTSD ModLoad: 77f80000 77ff9000   ntdll.dll
NTSD ModLoad: 77e80000 77f36000   KERNEL32.dll
NTSD: access violation
NTSD: !!! second chance !!!
eax=00000018 ebx=7ffdf000 ecx=004083c8 edx=00000000 esi=77e84ed2 edi=00008000
eip=0040124b esp=0012fe5c ebp=0012ff6c iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
*** ERROR: Module load completed but symbols could not be loaded for rmhdr.exe
rmhdr+124b:
0040124b c7000a000000     mov   dword ptr [eax],0xa ds:0023:00000018=????????
0:000>

It traps into rmhdr.  Look at the line that says "symbols could not be loaded"

We need symbols!

0:000> u
rmhdr+124b:
0040124b c7000a000000     mov     dword ptr [eax],0xa
00401251 8b8df8feffff     mov     ecx,[ebp-0x108]
00401257 83c104           add     ecx,0x4
0040125a 898df8feffff     mov     [ebp-0x108],ecx
00401260 83bdfcfeffff00   cmp     dword ptr [ebp-0x104],0x0
00401267 7591             jnz     rmhdr+0x11fa (004011fa)
00401269 8b95f0feffff     mov     edx,[ebp-0x110]
0040126f 52               push    edx
0:000> kb
ChildEBP RetAddr  Args to Child
0012ff6c 0040107c 00300eca 00300ed6 0007a120 rmhdr+0x124b
0012ff80 004018de 00000004 00300eb0 00300e00 rmhdr+0x107c
0012ffc0 77e87903 00008000 77e84ed2 7ffdf000 rmhdr+0x18de
0012fff0 00000000 0040182a 00000000 000000c8 KERNEL32!SetUnhandledExceptionFilte
r+0x5c
0:000>

Nice, we have no idea how this function was called.  First, we have a
small buffer, so we want to go to the top left corner of the window,
go to "Properties -> Layout Tab"  then go to Buffer Height.  It is probably
300 on Win2k and 80 on NT.  Set it to 9999.  Now, we have room to work.

Looking at that, we could be anywhere.  Symbols are usually put into
C:/WINNT/Symbols/EXE & C:/WINNT/Symbols/Dll

So, we copy the rmhdr.dbg file into C:/WINNT/SYMBOLS/EXE if you have
the symbols environment variable set.  Or, to be simpler, you may
copy it to your environment path and NTSD will pick it up.


0:000> !reload
0:000> kb
ChildEBP RetAddr  Args to Child
0012ff6c 0040107c 00300eca 00300ed6 0007a120 rmhdr!RemoveHdr+0xfc
0012ff80 004018de 00000004 00300eb0 00300e00 rmhdr!main+0x7c
0012ffc0 77e87903 00008000 77e84ed2 7ffdf000 rmhdr!mainCRTStartup+0xb4
0012fff0 00000000 0040182a 00000000 000000c8 KERNEL32!SetUnhandledExceptionFi
r+0x5c
0:000> r
eax=00000018 ebx=7ffdf000 ecx=004083c8 edx=00000000 esi=77e84ed2 edi=00008000
eip=0040124b esp=0012fe5c ebp=0012ff6c iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
rmhdr!RemoveHdr+fc:
0040124b c7000a000000     mov   dword ptr [eax],0xa ds:0023:00000018=????????
0:000> u
rmhdr!RemoveHdr+fc:
0040124b c7000a000000     mov     dword ptr [eax],0xa
00401251 8b8df8feffff     mov     ecx,[ebp-0x108]
00401257 83c104           add     ecx,0x4
0040125a 898df8feffff     mov     [ebp-0x108],ecx
00401260 83bdfcfeffff00   cmp     dword ptr [ebp-0x104],0x0
00401267 7591             jnz     rmhdr!RemoveHdr+0xab (004011fa)
00401269 8b95f0feffff     mov     edx,[ebp-0x110]
0040126f 52               push    edx
0:000>

We now have symbols!  We see that we are moving 10 into EAX, which
is set to 18h.  Well, let me show you some features of NTSD before
we figure out what the problem is.

If you look at the first "Return Address" on the stack, we can
dissassemble it and look at the calling code.

0:000> u 40107c
rmhdr!main+7c:
0040107c 83c40c           add     esp,0xc
0040107f 33c0             xor     eax,eax
00401081 5d               pop     ebp
00401082 c3               ret
rmhdr!DispatchMsg:
00401083 55               push    ebp
00401084 8bec             mov     ebp,esp
00401086 51               push    ecx
00401087 8b4508           mov     eax,[ebp+0x8]
0:000>

Doing this requires at least some assembly knowledge however.  

0:000> ~*
  0  id: 100.228   Suspend: 0 Teb 7ffde000 Unfrozen
  1  id: 100.310   Suspend: 0 Teb 7ffdd000 Unfrozen
0:000>

We see there are two threads.

0:000> ~1kb
ChildEBP RetAddr  Args to Child
0045ffb4 77e92ca8 00000000 00000200 0040fcbc ntdll!DbgBreakPoint+0x1
0045ffec 00000000 77eabe5a 00000000 00000000 KERNEL32!CreateFileA+0x11b
0:000> ~0kb
ChildEBP RetAddr  Args to Child
0012ff6c 0040107c 00300eca 00300ed6 0007a120 rmhdr!RemoveHdr+0xfc
0012ff80 004018de 00000004 00300eb0 00300e00 rmhdr!main+0x7c
0012ffc0 77e87903 00008000 77e84ed2 7ffdf000 rmhdr!mainCRTStartup+0xb4
0012fff0 00000000 0040182a 00000000 000000c8 KERNEL32!SetUnhandledExceptionFilte
r+0x5c
0:000>

However, this program is single threaded.  One of the threads could have been
created by an API we called (A lot of APIs create seperate threads). However,
"DbgBreakPoint" kind of makes it look to be a break point thread created by
NTSD to break into the thread.

We are only concerned with thread 0.
ChildEBP RetAddr  Args to Child
0012ff6c 0040107c 00300eca 00300ed6 0007a120 rmhdr!RemoveHdr+0xfc

Now, we know that the prototype of RemoveHdr is  

void RemoveHdr(char *InFileName, char *OutFileName, int Size)

Let's take a look at what is stored in the third parameter.  We also know
we called the function as so:

C:/>rmhdr tdconfig.td happy 500000
Stripping 500000 Bytes

And 50000 should have been passed to rmhdr as an integer.

0007a120 is what is listed as the third parameter.  Being passed
by value, this is the value in decimal 500000.

So, we know we're passing a correct parameter.  Next, we notice the 
first two parameters are ANSI strings.  

(Notice that just because it lists 3 parameters on all the functions, 
this is not nessecarily how many parameters the function has.)

0:000> da 300eca
00300eca  "tdconfig.td"
0:000> da 300ed6
00300ed6  "happy"
0:000>

(Notice, for Unicode strings, type "du <address>").

We notice we are passing down the correct parameters.  Parameter 1 is
the inputname we gave, and parameter 2 is the output name we gave.

We can do something else now.  We can find the address of RemoveHdr.

0:000> x rmhdr!RemoveHdr
0040114f  rmhdr!RemoveHdr
0:000>

Wild cards can be used as well, example:
0:000> x rmhdr!*   ; would list all the symbols for rmdhr.


Well, we now have the address.  But, we actually don't need
the address.  Because, you can referr to the function by its
symbol.  Let's unassemble.

0:000> u rmhdr!RemoveHdr
rmhdr!RemoveHdr:
0040114f 55               push    ebp
00401150 8bec             mov     ebp,esp
00401152 81ec10010000     sub     esp,0x110
00401158 681c814000       push    0x40811c
0040115d 8b4508           mov     eax,[ebp+0x8]
00401160 50               push    eax
00401161 e826030000       call    rmhdr!fopen (0040148c)
00401166 83c408           add     esp,0x8
0:000> u
rmhdr!RemoveHdr+1a:
00401169 8985f0feffff     mov     [ebp-0x110],eax
0040116f 83bdf0feffff00   cmp     dword ptr [ebp-0x110],0x0
00401176 750f             jnz     rmhdr!RemoveHdr+0x38 (00401187)
00401178 6a01             push    0x1
0040117a e804ffffff       call    rmhdr!DispatchMsg (00401083)
0040117f 83c404           add     esp,0x4
00401182 e90a010000       jmp     rmhdr!RemoveHdr+0x142 (00401291)
00401187 6820814000       push    0x408120
0:000> u
rmhdr!RemoveHdr+3d:
0040118c 8b4d0c           mov     ecx,[ebp+0xc]
0040118f 51               push    ecx
00401190 e8f7020000       call    rmhdr!fopen (0040148c)
00401195 83c408           add     esp,0x8
00401198 8985f4feffff     mov     [ebp-0x10c],eax
0040119e 83bdf4feffff00   cmp     dword ptr [ebp-0x10c],0x0
004011a5 751e             jnz     rmhdr!RemoveHdr+0x76 (004011c5)
004011a7 8b95f0feffff     mov     edx,[ebp-0x110]
0:000> u
rmhdr!RemoveHdr+5e:
004011ad 52               push    edx
004011ae e863020000       call    rmhdr!fclose (00401416)
004011b3 83c404           add     esp,0x4
004011b6 6a02             push    0x2
004011b8 e8c6feffff       call    rmhdr!DispatchMsg (00401083)
004011bd 83c404           add     esp,0x4
004011c0 e9cc000000       jmp     rmhdr!RemoveHdr+0x142 (00401291)
004011c5 6a00             push    0x0
0:000> r eip
eip=0040124b

 .....  we keep unassembling until we get to the crashed area.

0:000> u
rmhdr!RemoveHdr+ed:
0040123c 52               push    edx
0040123d e85d020000       call    rmhdr!fwrite (0040149f)
00401242 83c410           add     esp,0x10
00401245 8b85f8feffff     mov     eax,[ebp-0x108]
0040124b c7000a000000     mov     dword ptr [eax],0xa
00401251 8b8df8feffff     mov     ecx,[ebp-0x108]
00401257 83c104           add     ecx,0x4
0040125a 898df8feffff     mov     [ebp-0x108],ecx
0:000>

Now, we call fwrite before we move 10 into this variable.  Notice
EAX's address comes from EBP-0x108, which means it's a local variable.
We are assigning 10 to a local variable, infact, a pointer, since
we reference this address after we get it from the stack.  Right
after we call fwrite().  Should be easy to figure out.

(Also Note:

0:000> r
eax=00000018 ebx=7ffdf000 ecx=004083c8 edx=00000000 esi=77e84ed2 edi=00008000
eip=0040124b esp=0012fe5c ebp=0012ff6c iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
rmhdr!RemoveHdr+fc:
0040124b c7000a000000     mov   dword ptr [eax],0xa ds:0023:00000018=????????
0:000>

Hitting r will display all registers and take you to the spot where
it crashed or asserted again.  Using u from here will start
dissassembling after the point where it faulted.
)



We lucked out.  There is only 1 fwrite in the function, so we don't even have to 
try to see how many calls happened or who called what before when. 

     if(BlockSize)
       fwrite(Buffer, 1, BlockSize, OutFile);
     
     *x = 10; 
     x++;

What do we see here!  A pointer being assigned the value 10.  Now, why would this
crash?  As we look further we find:
  int BlockSize, *x;

And no other reference to *x in the entire function.  x has not bee allocated,
it's tring to write randomly to memory.  We have solved the problem.  
(Also, thank god for small functions, please do not write 5000000000 line functions!
They are hard to debug!).

We found the problem without rerunning the program.  Once it trapped into NTSD,
we were able to use NTSD to determine where the program had crashed without tring
to put print statements in and recompiling (Which can sometimes change a binary
in a way to where the bug doesn't seem to show up anymore!).  We also got it
without rerunning and tring to step through the code.  This can save you time, 
depending on the problem, size of the program and where it crashes, rerunning
or recompiling may not be an option.  However, this was a very simple case.
You may find yourself with traps that could have corrupted the stack, wrong
version of a dbg so you get bad symbols! or heap corruption which make debugging
all the more nasty.  For those who do not know assembly that well, you can 
generate assembly map files, which shows the C source with the generated assembly
below the lines.  This may help some who are not so good with assembly to
debug traps.


Now I'm going to break into a program and show some of the things you can do
with NTSD.  When you break into a process, or hit "CONTROL + C" when in
NTSD while it's in a process, you end up stopping the program.  When you
break in, however, you will be in a new thread created by NTSD just to break in.
These threads won't show, however, if you set a break point in the program or
if you trap into the program.  This only happens when you force NTSD to break
into the program.  Now, let's break into CuteFTP.


I run CuteFTP and look for the process ID on task manager.
It's 740, so I break into it.

C:/>ntsd -p 740


Microsoft(R) Windows 2000 Debugger
Version 5.00.2184.1
Copyright (C) Microsoft Corp. 1981-1999

CommandLine:
*** wait for debug event
Symbol search path is: C:/WINDOWS
NTSD ModLoad: 00400000 004da000   CUTEFTP.EXE
NTSD ModLoad: 77f80000 77ff9000   ntdll.dll
NTSD ModLoad: 77570000 775a0000   WINMM.dll
NTSD ModLoad: 77e10000 77e75000   USER32.dll
NTSD ModLoad: 77e80000 77f36000   KERNEL32.dll
NTSD ModLoad: 77f40000 77f7c000   GDI32.dll
NTSD ModLoad: 77db0000 77e0a000   ADVAPI32.dll
NTSD ModLoad: 77d40000 77daf000   RPCRT4.dll
NTSD ModLoad: 75050000 75058000   WSOCK32.dll
NTSD ModLoad: 75030000 75044000   WS2_32.dll
NTSD ModLoad: 78000000 78046000   MSVCRT.dll
NTSD ModLoad: 75020000 75028000   WS2HELP.dll
NTSD ModLoad: 76b30000 76b6e000   comdlg32.dll
NTSD ModLoad: 77c70000 77cba000   SHLWAPI.dll
NTSD ModLoad: 77b50000 77bda000   COMCTL32.dll
NTSD ModLoad: 775a0000 777e0000   SHELL32.dll
NTSD ModLoad: 77800000 7781d000   WINSPOOL.DRV
NTSD ModLoad: 752f0000 7530f000   oledlg.dll
NTSD ModLoad: 77a50000 77b45000   ole32.dll
eax=00000000 ebx=00000000 ecx=00000101 edx=ffffffff esi=00000000 edi=00000200
eip=77f9f9df esp=00ddffa8 ebp=00ddffb4 iopl=0         nv up ei ng nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000286
ntdll!DbgBreakPoint:
77f9f9df cc               int     3
0:002> ~*
  0  id: 2e4.210   Suspend: 0 Teb 7ffde000 Unfrozen
  1  id: 2e4.2a8   Suspend: 0 Teb 7ffdd000 Unfrozen
  2  id: 2e4.228   Suspend: 0 Teb 7ffdc000 Unfrozen
0:002> ~0kb
*** ERROR: Module load completed but symbols could not be loaded for cuteftp.exe

ChildEBP RetAddr  Args to Child
0006fbbc 0044fad5 004939e0 00000000 00000000 USER32!DispatchMessageW+0x24b
004939e0 0000000f 00000000 00000000 00328f9c CUTEFTP+0x4fad5
0:002> ~1kb
ChildEBP RetAddr  Args to Child
00d8ff74 77d4b407 77d4b7bf 000b7658 00000000 ntdll!ZwReplyWaitReceivePortEx+0xb
00d8ffa8 77d4b771 000b65e8 00d8ffec 77e92ca8 RPCRT4!RpcBindingSetOption+0x18e
00d8ffb4 77e92ca8 000b77a0 00000000 400b7154 RPCRT4!RpcBindingSetOption+0x4f8
00d8ffec 00000000 77d4b759 000b77a0 00000000 KERNEL32!CreateFileA+0x11b
0:002> ~2kb
ChildEBP RetAddr  Args to Child
00ddffb4 77e92ca8 00000000 00000200 0040fcbc ntdll!DbgBreakPoint
00ddffec 00000000 77eabe5a 00000000 00000000 KERNEL32!CreateFileA+0x11b
0:002>

One of the threads is the breakpoint created by NTSD, that's thread
#3, the one we are in now.  Notice, CuteFTP has no symbols.

But, lets see some things we can do with NTSD.  First, let's set a break point.

0:002> ~0kb
ChildEBP RetAddr  Args to Child
0006fbbc 0044fad5 004939e0 00000000 00000000 USER32!DispatchMessageW+0x24b
004939e0 0000000f 00000000 00000000 00328f9c CUTEFTP+0x4fad5
0:002> ~0r
eax=0000000b ebx=77e1a57c ecx=0006f824 edx=00000000 esi=004939e0 edi=004939e0
eip=77e1414f esp=0006fb9c ebp=0006fbbc iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
USER32!DispatchMessageW+24b:
77e1414f c21000           ret     0x10
0:002> bp 44fad5
0:002> g
eax=00000001 ebx=77e1a57c ecx=00000007 edx=ffffffff esi=004939b0 edi=004939e0
eip=0044fad5 esp=0006fbd4 ebp=004939e0 iopl=0         nv up ei pl zr na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000246
CUTEFTP+4fad5:
0044fad5 85c0             test    eax,eax
0:000>

We set a breakpoint on the return address of thread 0.  
We hit 'g' which tells the process to continue.  However, it will
stop if it hits an NTSD set break point or a hard coded int 3 breakpoint
or if the application traps.  However, this time, it hit our
NTSD defined break point.

This is obviously the message loop (DispatchMessageW, Gives that away)
We know the parameter to DispatchMessage is an address 
to an MSG structure.  We can dump this.

0:000> dd 4939e0
004939e0  000101a2 00000219 00000007 00000000
004939f0  0032cd1b 0000010b 00000198 00000000
00493a00  00000000 00464eb7 008b0a40 000001cb
00493a10  000001e9 00000200 00400000 00000000
00493a20  00082788 ffffffff 008b09e0 00000000
00493a30  008b1e20 00000000 008b09d0 008b0ae0
00493a40  008b0ab0 00000000 00000000 00000000
00493a50  00000000 00000000 00000000 00000000
0:000> ~*
  0  id: 2e4.210   Suspend: 0 Teb 7ffde000 Unfrozen
  1  id: 2e4.2a8   Suspend: 0 Teb 7ffdd000 Unfrozen
0:000>

Notice there are only 2 threads now.  These are both CuteFTP threads,
not NTSD breakpoint threads.



Now, I am going to teach you a trick.  Every thread has
an Information Block that stores information about itself.
You can look it up on MSDN, "TIB".  It's pretty much the
same structure for all Win32 platforms, as application programs
sometimes hard code using this data.  To get the locations of
what most of the structure means, search MSDN.  I will show you
one part of the TIB.  The TIB is pointed to by FS.  

Let's say you have this situtation.  You break into a program
you have, you set a breakpoint on a particular API you know is failing.
Then you set a break point on it's return.  Now, you see it failed,
but you would need to call GetLastError() to get the error number!
You would need to recompile this program and put in a print message
just to find one number!  What to do?


0:000> x KERNEL32!GetLastError
77e8668c  KERNEL32!GetLastError
0:000> u KERNEL32!GetLastError
KERNEL32!GetLastError:
77e8668c 64a118000000     mov     eax,fs:[00000018]
77e86692 8b4034           mov     eax,[eax+0x34]
77e86695 c3               ret
77e86696 85ff             test    edi,edi
77e86698 0f84053c0400     je      KERNEL32!VerLanguageNameA+0xef (77eca2a3)
77e8669e 8d8d58ffffff     lea     ecx,[ebp-0xa8]
77e866a4 6a00             push    0x0
77e866a6 51               push    ecx
0:000>

Well, what do you know?  GetLastError() is pretty simple.

Let's find out what the Last Error was!

0:000> dd fs:18
0038:00000018  7ffde000 00000000 000002e4 00000210
0038:00000028  00000000 00000000 7ffdf000 00000000
0038:00000038  00000000 00000000 e1fe9228 00000000
0038:00000048  00000000 00000000 00000000 00000000
0038:00000058  00000000 00000000 00000000 00000000
0038:00000068  00000000 00000000 00000000 00000000
0038:00000078  00000000 00000000 00000000 00000000
0038:00000088  00000000 00000000 00000000 00000000
0:000> dd 7ffde000+34
7ffde034  00000000 00000000 00000000 e1fe9228
7ffde044  00000000 00000000 00000000 00000000
7ffde054  00000000 00000000 00000000 00000000
7ffde064  00000000 00000000 00000000 00000000
7ffde074  00000000 00000000 00000000 00000000
7ffde084  00000000 00000000 00000000 00000000
7ffde094  00000000 00000000 00000000 00000000
7ffde0a4  00000000 00000000 00000000 00000000
0:000>

As you notice, the first DWORD listed is 0.  Now, when I did this,
there was no error.  But, you can see how this can be useful.

Remeber you can always clear and set breakpoints.  Use "T" and "P" to step
through and execute the code.  

That is all for now.  Hopefully, you will practice real debugging and
improve your debugging skills after reading this basic debuggin tutorial.


两路半桥LLC谐振变换器软开关Simulink仿真(模拟谐振参数不同、分裂电容) 内容概要:本文介绍了&ldquo;两路半桥LLC谐振变换器软开关Simulink仿真&rdquo;资源包,重点在于基于Simulink平台对两路半桥LLC谐振变换器进行高精度建模与动态仿真,特别考虑了谐振参数不一致及分裂电容等非理想工况对系统性能的影响。该仿真模型不仅涵盖了主电路拓扑、软开关实现机制与谐振网络设计,还深入分析了不同参数配置下系统的启动特性、稳态效率、动态响应及软开关条件的满足情况。通过参数扫描与对比仿真,帮助用户掌握LLC变换器的关键设计要素与优化方法,具备较强的工程指导价值和科研教学意义。; 适合人群:电力电子、电气工程及其自动化等相关专业的高校师生,以及从事高频开关电源、新能源变换器、车载充电机等领域的研发工程师和技术人员。; 使用场景及目标:① 掌握两路并联半桥LLC谐振变换器的建模与仿真方法;② 理解软开关技术在提升变换器效率与降低EMI方面的关键作用;③ 分析谐振参数偏差与分裂电容结构对系统均流特性、增益曲线及软开关范围的影响;④ 支持科研项目中的拓扑验证、控制策略开发与故障工况分析; 阅读建议:建议结合Simulink仿真模型与配套说明文档进行学习,重点关注谐振腔参数设置、驱动时序设计及软开关波形观测;可通过调整谐振电感、电容值或引入参数失配,观察系统行为变化,深入理解LLC变换器的稳定边界与优化设计原则。 立即下载

相关推荐

双向全桥CLLC谐振变换器仿真模型(Simulink仿真实现)

内容概要:本文系统介绍了双向全桥CLLC谐振变换器的Simulink仿真模型,重点阐述了变频控制、闭环仿真及其与多种电力电子拓扑(如维也纳整流器、DAB变换器、图腾柱PFC等)的联合仿真设计。资源包涵盖2.5kW双向全桥LLC、V2G电动汽车充放电系统、三相PFC、多相交错LLC等多种变换器的仿真案例,并深入展示了三电平VIENNA整流器在双闭环PI控制与SVPWM调制策略下,结合上下电容电压差值补偿实现中点电位平衡的先进控制方法。通过仿真验证,该策略不仅有效维持了800V直流母线电压的稳定,还在动态工况下显著提升了电容电压的均衡度,同时保持了较低的输入电流THD,充分体现了SVPWM与中点平衡控制协同优化的技术优势。; 适合人群:具备电力电子与自动控制理论基础,从事新能源发电、电动汽车、工业电源、微电网等领域的研究生、工程师及科研人员。; 使用场景及目标:① 学习并构建高频谐振变换器(如LLC、CLLC)的Simulink仿真模型,掌握其变频与混合控制策略;② 深入理解并应用中点电位平衡、双闭环PI控制、SVPWM调制等核心技术于VIENNA等三电平变换器中;③ 复现高水平期刊论文中的控制算法,服务于科研项目申报、学术论文撰写或工程产品开发。; 阅读建议:该资源为综合性仿真资料库,建议使用者结合自身研究方向(如电动汽车充电、新能源并网)选择对应案例进行精学,重点关注控制策略的设计逻辑、仿真参数的整定方法以及SVPWM与中点平衡的协同机制,并充分利用附带的计算书和参考文献深化理解,以实现从仿真模型到实际工程应用的顺利转化。

微软技术日报 11-09:.NET 8 与 9 明日同时停止支持,注册表编辑器出了 WinUI 3 版

今天这四条消息的共同点是收口。.NET 8 和 9 的支持期到点,注册表编辑器的新界面只放在仓库里给人看,GitHub 把一类危险触发器按住默认,微软在安全报告里把防线从地址黑名单挪到行为。收口并不等于关门。.NET 10 还在支持期里,被按住的触发器可以用策略按文件放回来,实验室里的样例随时可能变成产品。区别在于这些动作要有人签字,不再是默认开着。如果今天只做一件事,去看一眼构建机上的 .NET 版本。明天之后,8 和 9 的漏洞补丁不会再来了。

海盗Sharp的博客 389

倒立摆入门详解+pid调参

倒立摆制作全过程 最近在练习pid的控制算法,就选用了2013年的电子设计国赛题目:倒立摆 。 首先给你们看看我的成果 注意哦:倒立摆不是靠上面黄色的传感器保持平衡的,而是通过下面的电机左右扭动使得摆杆不掉下来,难度可不小了。 材料准备 1.机械部分 如图所示选好电机和角位移传感器进行组装就好了,这个部分是我直接网上购买的,如果要自己装也是可以的,不过会比较麻烦,结构差不多的。 2.主控部分 这个部分的话完全是我自己设计的,作用和网上买的一样,因为网上买的话太贵了,有需要的话可以联系我,有pcb文件

small_po_kid的博客 2万+

AI驱动数据处理的安全边界:行业现状、风险权衡与本地化实践路径

本文基于第三方数据,分析AI用于数据处理的安全议题。大模型应用加速普及,但伴随数据出境、影子AI、幻觉与成本失控等风险。行业共识指向“脚本复用”路径:将分析逻辑本地化固化,实现数据可控、结果可复现、成本可降。产品如数以轻舟提供本地处理、多模型接入与一次付费永久使用方案。建议企业从数据落点、可控性、可复现性与合规性四维度评估选型,推动AI在安全边界内释放价值。

数据矿工-数据化运营博客 326

linux入侵排查-综合日志分析

2.分析攻击者首次成功登录web管理后台的时间 还可以知道攻击者可能是通过10.2.4.11这个跳板服务器跳过来的,然后我们继续排查,可以发现日志中POST请求大多都是1047字节,然后往后大多是300字节,POST请求的响应状态码有200和302这两个,其中的302是爆破成功后持续爆破产生的日志,再1047字节和300字节中间夹着一个978字节,就是200响应和302响应的中间。7.分析攻击者留下的持久化后门的时间和后门执行情况 排查计划任务,发现有一个计划任务还是被加密的,应该是反弹shell的。

chinese_cabbage0的博客 644

告别抓包:一个Xposed模块教你监控抖音App的本地数据变化

本文详细介绍了如何利用Xposed框架开发模块,实现抖音App本地数据监控的技术方案。通过Hook关键方法,开发者可以实时获取用户信息、推荐算法参数等核心数据,无需Root设备即可高效调试。文章包含完整的环境配置、代码实现和优化技巧,为移动应用开发与测试提供创新解决方案。

weixin_27015733的博客 387

微软技术日报 11-08:Foundry 与 Entra 各挂一个 10.0 分漏洞,Foundry 接 fal 五款视频模型

这一周的动作可以分成两类。一类在收。Copilot 从电视上退场,搜索里的 AI 改成可选,云端漏洞只发通知不收补丁。共同点是承认铺得太开,代价比预期高。另一类在放。Foundry 一次摆上五个视频模型,容器版应用网关一个入口挂 64 个 MCP 服务器。共同点是把能力做成可组合的积木,让使用者按需挑。两个方向背后是同一个判断。把能力塞到每一个界面上,短期看覆盖率高,长期要解释为什么删不掉。新的做法是先给一个能关的开关,再谈覆盖。对每天写代码的人来说,今天有两样现成的东西可以试。

海盗Sharp的博客 419

如何使用freesurfer对T1w结构像进行分区指标的提取?——皮层面积,灰质体积,平均厚度,厚度标准差,平均曲率、高斯曲率、曲率指数、折叠指数

如何使用freesurfer对T1w结构像进行分区指标的提取?——皮层面积,灰质体积,平均厚度,厚度标准差,平均曲率、高斯曲率、曲率指数、折叠指数

从事脑科学核磁共振方法学研究,在Nature communications等权威期刊发表研究论文,熟练掌握磁共振处理方法和统计学方法,欢迎大家和我交流。 3788

微软技术周报 11-02~11-09:VS Code 混装 Claude 与 Codex、EF Core 11 静默改行为、.NET 11 倒计时

11 月 6 日,Power Platform 通过消息中心通知管理员,Copilot Studio、Power Automate 和 Power Apps 里所有跑在 Anthropic Claude、xAI Grok 以及旧版 GPT 上的 prompt,12 月 2 日自动迁移到 GPT-6 系列,档位价格不变,模型选择收窄到一条产品线。效果是这样的:让 Claude 评审一个 Copilot chat 写出的方案,或者把同样的任务交给 Codex,两边并排跑在同一个会话里。更要紧的是下周的时间线。

海盗Sharp的博客 987

Android面试指南及参考答案

【摘要】LesPark招聘Android开发工程师,要求3年以上经验,负责客户端功能开发与优化。岗位需掌握Java、多线程、网络编程(TCP/UDP/HTTP)及性能调试工具。面试重点考察:1)核心技术如网络协议选择(TCP可靠消息/UDP实时音视频)、内存泄漏分析;2)业务能力如图片懒加载三级缓存设计、启动速度优化案例;3)高阶系统原理如插件化框架设计;4)行为问题应对策略。建议候选人准备并发编程、JVM差异等深度知识,并量化项目优化成果,尤其关注社交应用相关的图片处理、实时通信等场景题。

郑伟强dev的专栏 806

ofd转pdf报错:org.ofdrw.reader.ZipUtil.unZipFileByApacheCommonCompress【已解决】

ofd转pdf报错:java.lang.NoClassDefFoundError:org/apache/commons/io/function/IOIterator at org.ofdrw.reader.ZipUtil.unZipFileByApacheCommonCompress,java.lang.NoSuchMethodError:org.apache.commons.compress.archivers.zip.ZipArchiveInputStream.getNextEntry()

黄团团的个人博客 1283

MAI-Cyber-1 深度解析:微软首个网络安全专用模型

微软没有去做"更大的安全大模型",而是用一颗 137B 总参、仅 5B 激活的稀疏 MoE 专用模型,配上 90/10 的"小模型铺量、大模型兜底"路由,在 CyberGym 基准上拿到 95.95% 的同时把成本砍掉一半——这标志着 AI 安全从"通用模型加提示词"进入了"专用模型加系统工程"的时代。

Simple_Pure的博客 327

目标检测: yolo26在RK3588上部署

YOLO26发布:端到端目标检测新突破 摘要:Ultralytics最新发布的YOLO26算法采用原生端到端设计,无需NMS后处理,显著提升推理效率。该模型基于三大核心原则: 简洁性:消除NMS步骤,实现直接预测 部署效率:简化集成流程,降低延迟 训练创新:引入MuSGD混合优化器,加速收敛 部署教程显示,YOLO26可适配RK3588等边缘设备,通过ONNX转换和RKNN工具链实现高效部署。其创新架构特别优化了分割、姿态估计等专业任务,标志着目标检测技术的重要革新。 (字数:149)

qianxun 1454

微软输入法标点乱跳解决方案

微软拼音输入法常因“中文输入时使用英文标点”开关被意外开启,导致标点自动切换为英文且快捷键失效。本文提供一键批处理脚本,通过修改注册表(HKCU\Software\Microsoft\InputMethod\Settings\CHS)强制锁定中文标点,覆盖多个相关键值,确保设置持久生效。相比手动操作,该方案可抵御更新、同步等导致的配置重置,提升稳定性。执行后需重启输入法或系统以使更改生效,建议提前备份注册表。

谙弆悕博士的技术博客! 369

【FPGA教程案例37】通信案例7——基于FPGA的FFT,IFFT傅里叶变换和逆变换

关于FFT和IFFT的相关描述,在上一课程我们已经有所了解,本课程不再重复。在上一课程,我们初步掌握了如何通过FFT对输入信号进行频谱分析,在本课程,我们将学习如何通过FPGA实现FFT和IFFT得到初始信号的过程。关于FFT的IP核配置,和上一课程相同。下面直接介绍其verilog调用过程。我们可以看到,IFFT的配置非常简单,在FFT的基础上,只需要将该参数的最后一位改为0,就可以实现IFFT变化。下面我们通过testbench文件,同时调用FFT和IFFT实现信号的傅里叶变换和逆变换。......

FPGA/MATLAB学习教程/源码/项目合作开发 4518

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

微软2026年11月补丁星期二解析:1094个 CVE、Outlook 零日 CVE-2026-124194 与修复优先级

微软11月补丁星期二修复1094个 CVE,唯一在野利用的 Outlook 零日 CVE-2026-124194 只评为“中等”,修复随 Office 更新下发、不在 Windows 累积更新里。本文整理漏洞分布、严重级漏洞分类处置与三档修复优先级。

SplashtopLoki的博客 211

华为的C语言编程规范.pdf

华为技术有限公司内部技术规范。本规范制定了编写C语言程序的基本原则、规则和建议。从代码的清晰、简洁、可测试、安全、程序效率、可移植各个方面对C语言编程作出了具体指导。

微软 BioEmu2 实测:AI 设计的抗体,能否通过动态模拟提前筛选?

微软 BioEmu2 实测:AI 设计的抗体,能否通过动态模拟提前筛选?**2026 年 10 月 26 日,展示了一个 AI 抗体候选的 10 纳秒传播动画。**但这个案例留下的最大问题,仍是实验室门前那道选择题:一批看起来合理的设计,究竟该把哪几个送去测试?Ford 正在参与 Adaptyv 与 Anthropic 的蛋白设计竞赛,也在开发抗体语言模型。**但他在原文中坦言,生成候选容易,挑出值得测试的候选依然困难。**本文解读他的公开实测,并对照微软文档核查证据边界,未独立运行模型。

weixin_71777199的博客 216

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an &ldquo;advanced algorithms&rdquo; book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

【第 008 讲】在终端中运行 Python 程序:交互模式与脚本模式 | 绝对路径与相对路径 | 以模块方式运行 | 常见报错的识别与排查

本文系统讲解在终端中运行 Python 程序的方式:从交互模式与脚本模式讲起,梳理绝对路径与相对路径的运行写法及 cd、dir 等命令的用法;进而讲解以模块方式运行的特点与字节码缓存的产生;最后通过路径、拼写、标点三类典型报错,帮助读者掌握读懂报错、定位并修复错误的排查思路。

大模型全栈工程师 · Thanks_ks 778

企业微信 × AI 对接第六步:模板卡片交互闭环——按钮回调、ResponseCode 状态机、卡片更新(附跑通代码)

AI 生成的审批建议,让主管在卡片上直接点"同意/驳回",比跳系统快一个数量级——这是企业微信模板卡片消息的交互闭环:发送时带 task_id 和按钮 key,用户点击后企微回调 EventKey/TaskId/ResponseCode,你拿 ResponseCode(72 小时有效、只能用一次)调 update_template_card 把卡片原地改成"已处理"。这篇把五种卡片类型、回调字段、更新接口一次讲透,附一个带完整防御(重复核销/超时/未知 key 拒绝)的状态机代码,本地实测跑通四种场景。

weixin_45264536的博客 372

情感交互机器人融资,四川具身科技的「爱湫」登场

当前人形机器人的主流战场是工业、仓储、巡检这些「干活」的场景,而「爱湫」把重心放在了「陪伴」上——通过面部投影和情绪识别,让机器人和人之间多一层情感层面的交互。情感交互机器人的市场,其实一直存在,只是过去被「能干活」的主流叙事盖住了。它主推的产品「爱湫」,是一款主打情感交互的人形机器人,搭载 3D 投影面部技术,情感交互机器人的逻辑,是面向养老陪伴、文旅服务、展厅导览这类「不要求重体力、但要求互动感」的场景。真正的考验不在发布会,而在它能不能在真实的养老和陪伴场景里,被用户用起来、留下来。

jushenAGI的博客 290

微软 365 全球掉线复盘,Purview 数据治理退役,C++ 动态调试转正

把今天这几条放在一起,会看到一个共同的题目,系统的自动判断出错时,人还能不能兜住。故障那条是自动判断误判。健康检查本来的职责是把坏机器摘出去,这次它把还能用的机器也摘了,剩下的机器承接双倍压力,再被摘掉,容量一路缩减。微软的补救方向很说明问题,一边验证一套替代的健康检查方式,一边准备自动检测并重启无响应服务器的机制,等于在原来的判断链路上加第二道保险。脚本那条是校验从宽容变严格。旧文件在宽松的校验下一直能用,规则一收紧,一批历史遗留的编码问题同时浮出水面,而表象是「签名没了」。

海盗Sharp的博客 300

微软技术日报 2026-11-07:Copilot 本机跑 27B 模型、Front Door 默认后量子 TLS、Beta 转 26H2

四条消息挤在同一天,方向各不相同:Foundry Local 把 27B 的编码模型压进了 24GB 显存并接进 GitHub Copilot;Azure Front Door 把入站 TLS 1.3 的混合后量子密钥交换设成了默认;Windows Insider 一次放出五个构建,Beta 通道正式转到 26H2;Power Platform 通知管理员,12 月 2 日提示模型换血,Claude 与 Grok 一起退出。四条放在一起看,有两条在改默认值,两条在改节奏。

海盗Sharp的博客 514

微软Copilot生态深度解析:从办公助手到超级应用的整合趋势与实战技巧

在人工智能技术飞速发展的今天,微软Copilot已成为连接用户与AI生产力的核心桥梁。虽然“copilot-analysis”这一特定仓库可能指向具体的分析脚本或文档,但从更宏观的视角来看,它代表了当前对微软Copilot生态系统的深度探索。Copilot不仅仅是一个聊天机器人,它已经深度嵌入到Excel、Word、GitHub等核心生产力工具中,甚至正在向“超级应用”演进。

wulechun的博客 166

全球化筑基,AI 赋能新体验,openKylin 3.0 SP1 正式发布

本次版本更新以国际化为底座,围绕国际化体验、AI 融合、硬件生态、系统质量四大方向完成全面升级:新增阿拉伯语、朝鲜语、傣语等多语言支持;持续强化重点平台与关键外设适配能力,完善飞腾 D3000M、进迭时空 V100 等平台图形显示、网卡驱动、高性能计算等能力,进一步拓展 openKylin 在多架构、多设备场景下的兼容边界。围绕系统稳定性、硬件兼容性与用户体验持续迭代,累计完成 200 余项缺陷修复与细节优化,覆盖桌面交互、图形显示、设备管理等核心场景,推动系统更加稳定、流畅、易用。

openKylin的博客 199

微软技术日报 2026-11-11:补丁日 1093 个漏洞、Outlook 零日在野、.NET 11 正式发布

把这几条放在一起看,会发现它们指向同一个方向,留给维护的窗口在变短。补丁日一次一千多条、Azure Linux 一次四百多条 CVE 引用、ACL 把代码完整性做成可选审计再加一层默认镜像、.NET 把两个版本的支持期放在同一天结束,做的都是同一件事,把「以后再说」往后压。要做的事不算复杂。把对公网暴露的 Exchange 与 SharePoint 排进本周的窗口,Exchange 那条要预留重启时间;

海盗Sharp的博客 705

微软技术日报 11-10:.NET 11 今晚发布,.NET 8 与 9 同日停更,Copilot 连接器开写入

今天微软技术侧的重心全压在一个日期上。11 月 10 日既是 .NET Conf 2026 开幕、.NET 11 正式发布的日子,也是 .NET 8 与 .NET 9 终止支持的日子。同一个日期上,一条线开始,两条线结束。除了这条主线,还有三件事在这一两天落地。微软安全博客公布了一轮用智能体扫 Rust 标准库的研究,Copilot 连接器的写入能力转为正式可用,Windows 11 的统一应用更新页又在预览版里冒了头。

海盗Sharp的博客 930
上一篇: How To Debug
下一篇: Exploitation of Buffer Overflow Vulnerabilities Under Windows XP
iiprogram
博客等级 码龄25年 1068粉丝 1515原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值