ba(break on access)
ba 커맨드에 대해 설명하기 전에 한가지 재미있는(?혹은 식상할 수도) 코드를 보여드리려 합니다.
이 프로그램은 두 개의 전역변수를 가지고 있고, PrintGlobalVariable() 함수를 통해 두개의 전역변수를 출력합니다.
프로그램이 간단하니 어렵지 않게 출력 결과를 유추하실 수 있겠죠?
char g_szTitle[32] = "TestApp - User";
DWORD g_dwPort = 8080;
void PrintGlobalVariable();
int _tmain(int argc, _TCHAR* argv[])
{
PrintGlobalVariable();
strcpy(g_szTitle, "TestApp - GOODSMELL's COMPUTER!!!!");
PrintGlobalVariable();
return 0;
}
void PrintGlobalVariable()
{
printf("g_szTitle = %s, g_dwPort = %d\n", g_szTitle, g_dwPort);
} |
예상하신 결과가 맞나요??
위의 프로그램은 아주 예전에 발생했던 문제를 간소화 해서 코딩한 것입니다.
프로그램의 타이틀은 [AppName + Remote컴퓨터명 + etc…] 형태로 되어있고, 지정된 포트를 통해 다른 컴퓨터에 접속하는 프로그램이었습니다만, 특정 컴퓨터(항상 이게 문제죠…!)에서 접속을 2회 이상시도하게 되면 접속이 되지 않는 문제 였습니다.
위 프로그램의 출력결과는 아래와 같습니다.
두번째 출력전에 한것이라곤 g_szTitle에 문자열을 복사한 것 뿐인데, 포트 값이 바뀌어 있네요..
이제 어느정도 감이 오시죠?
문제를 좀더 명확히 하기 위해 다음 한 줄의 코딩을 추가했습니다.
printf("g_szTitle address = %p, g_dwPort address = %p\n", g_szTitle, &g_dwPort); |
출력 결과는 다음과 같습니다.
이제 문제가 명확해 졌군요. g_szTitle과 g_dwPort는 다른 변수이지만 할당된 메모리 공간이 인접해 있습니다.
0x002B7058 – 0x002B7038 = 0x20(32) ,즉 g_szTitle이 할당된 메모리(32byte)에 연속해서 g_dwPort(4byte)가 할당되어 있습니다. strcpy()함수 호출을 통해 35byte(g_szTitle:32byte)의 메모리를 변경했기때문에 g_dwPort의 3byte를 덮어써 버린것이죠.
g_szTitle(32bytes)
0x002B7038 |
g_dwPort(4bytes)
0x002B7058 |
|
|
|
|
|
위와 같은 문제가 아주 덩치가 큰 프로그램에서 발생한다고 하면, 사실 좀 난감합니다. 일단 어찌 어찌해서 g_dwPort가 변경되는 것 까지는 확인했다고 치겠습니다.
이 상황에서 ba 커맨드는 아주 유용합니다.
ba 커맨드의 사용포맷은 이렇습니다.(Access Size 는 붙여서 씁니다)
n Access
ü e – excute(설정된 Address가 실행될 때 적중)
ü r – read/write(설정된 Address에 Size만큼 read or write 가 발생할때 적중)
ü w - write(설정된 Address에 Size만큼 write 가 발생할때 적중)
ü I - io(설정된 Address에 io가 발생할때 적중) - widbg설명을 보니 port address등에 bp를 걸어 사용하면 되는듯 합니다만, 전 거의 그럴일이 없으니 pass~
n Size
ü r/w - 1,2 or 4 (x86), 1,2,4 or 8 (x64)
ü
1e - 1
n Address
ü Memory 주소
자 그럼 위에서 하던 얘기를 마저 하겠습니다. 지금 상황에서는 다음과 같은 커맨드이면 될 것 같습니다.
그럼 한 단계씩 디버깅해 보겠습니다.
n [File>Open Excutable]메뉴를 이용해 TestApp.exe를 실행한후 wmail()함수에 breakpoint를 설정합니다.
n Wmain()에 breakpoint가 적중되면, g_dwPort에 ba커맨드로 breakpoint를 설정합니다.
계속 진행시키면 strcat+0xcd에서 breakpoint가 적중되었군요.
strcpy가 내부적으로 strcat을 호출하는가 봅니다.
n 그럼 이 시점에서의 CallStack과 strcat의 인자를 확인해 보겠습니다.
wmain에서 호출된 strcat(strcpy)에 의해서 g_dwPort값이 변경되는 것을 알 수 있습니다.
인자를 출력해 보니 역시나, g_szTitle에 복사하려던 문자열이네요.
ba 커맨드는 의외로 유용하게 사용 할 상황이 많이 있습니다. 저도 두번인가? 정말 유용하게 써먹었던 기억이 있습니다.^^
(아 그리고 string카피할때는 strncpy나 strcpy_s류의 함수를 쓰는게 좋은 습관인건 아시죠?)
아래는 ba관련 windbg도움말 입니다. 참고하세요.
ba (Break on Access)
The ba command sets a data breakpoint. This breakpoint is triggered when the specified memory is accessed.
Syntax
User-Mode
[~Thread] ba[ID] Access Size [Options] [Address [Passes]] ["CommandString"]
Kernel-Mode
ba[ID] Access Size [Options] [Address [Passes]] ["CommandString"]
Parameters
- Thread
- Specifies the thread that the breakpoint applies to. For more information about syntax, see Thread Syntax. You can specify threads only in user mode.
- ID
- Specifies an optional number that identifies the breakpoint. If you do not specify ID, the first available breakpoint number is used. You cannot add space between ba and the ID number. Each processor supports only a limited number of data breakpoints, but there is no restriction on the value of the ID number. If you enclose ID in square brackets ([]), ID can include any expression. For more information about the syntax, see Numerical Expression Syntax.
- Access
- Specifies the type of access that satisfies the breakpoint. This parameter can be one of the following values.
Option |
Action |
e (execute) |
Breaks into the debugger when the CPU retrieves an instruction from the specified address. |
r (read/write) |
Breaks into the debugger when the CPU reads or writes at the specified address. |
w (write) |
Breaks into the debugger when the CPU writes at the specified address. |
i (i/o) |
(Microsoft Windows XP and later versions, kernel mode only, x86-based systems only) Breaks into the debugger when the I/O port at the specified Address is accessed. |
You cannot add space between Access and Size.
Note On Windows Server 2003 with Service Pack 1 (SP1), on an Itanium-based computer that uses WOW64 to emulate x86, data breakpoints do not work with the execute option but they do work with the read and write options.
- Size
- Specifies the size of the location, in bytes, to monitor for access. On an x86-based processor, this parameter can be 1, 2, or 4. However, if Access equals e, Size must be 1.
On an x64-based processor, this parameter can be 1, 2, 4, or 8. However, if Access equals e, Size must be 1.
On an Itanium-based processor, this parameter can be any power of 2, from 1 to 0x80000000.
You cannot add space between Access and Size.
- Options
- Specifies breakpoint options. You can use any number of the following options, except as indicated:
- /1
- Creates a "one-shot" breakpoint. After this breakpoint is triggered, the breakpoint is permanently removed from the breakpoint list.
- /f PredNum
- (Itanium only, user mode only) Specifies a predicate number. The breakpoint is predicated with the corresponding predicate register (for example, bp /f 4 address sets a breakpoint that is predicated with the p4 predicate register). For more information about predicate registers, see Itanium Architecture.
- /p EProcess
- (Kernel mode only) Specifies a process that is associated with this breakpoint. EProcess should be the actual address of the EPROCESS structure, not the PID. The breakpoint is triggered only if it is encountered in the context of this process.
- /t EThread
- (Kernel mode only) Specifies a thread that is associated with this breakpoint. EThread should be the actual address of the ETHREAD structure, not the thread ID. The breakpoint is triggered only if it is encountered in the context of this thread. If you use /p EProcess and /t EThread , you can enter them in either order.
- /c MaxCallStackDepth
- Causes the breakpoint to be active only when the call stack depth is less than MaxCallStackDepth. You cannot combine this option together with /C.
- /C MinCallStackDepth
- Causes the breakpoint to be active only when the call stack depth is larger than MinCallStackDepth. You cannot combine this option together with /c.
- Address
- Specifies any valid address. If the application accesses memory at this address, the debugger stops execution and displays the current values of all registers and flags. This address must be an offset and suitably aligned to match the Size parameter. (For example, if Size is 4, Address must be a multiple of 4.) If you omit Address, the current instruction pointer is used. For more information about the syntax, see Address and Address Range Syntax.
- Passes
- Specifies the number of times the breakpoint is passed by until it activates. This number can be any 16-bit value. The number of times the program counter passes through this point without breaking is one less than the value of this number. Therefore, omitting this number is the same as setting it equal to 1. Note also that this number counts only the times that the application executes past this point. Stepping or tracing past this point does not count. After the full count is reached, you can reset this number only by clearing and resetting the breakpoint.
- CommandString
- Specifies a list of commands to execute every time that the breakpoint is encountered the specified number of times. These commands are executed only if the breakpoint is hit after you issue a g (Go) command, instead of after a t (Trace) or p (Step) command. Debugger commands in CommandString can include parameters.
You must enclose this command string in quotation marks, and you should separate multiple commands by semicolons. You can use standard C control characters (such as \n and \"). Semicolons that are contained in second-level quotation marks (\") are interpreted as part of the embedded quoted string.
This parameter is optional
Environment
Modes |
User mode, kernel mode |
Targets |
Live debugging only |
Platforms |
All |
Comments
The debugger uses the ID number to refer to the breakpoint in later bc (Breakpoint Clear), bd (Breakpoint Disable), and be (Breakpoint Enable) commands.
Use the bl (Breakpoint List) command to list all existing breakpoints, their ID numbers, and their status.
Use the .bpcmds (Display Breakpoint Commands) command to list all existing breakpoints, their ID numbers, and the commands that were used to create them.
The ba command provides the same functionality that the debug registers provide. You can break execution when the particular memory location is read from, written to, or executed.
The breakpoint is satisfied only when the access occurs at the given address and for the specified number of bytes. If the memory that is accessed overlaps the specified area to monitor, the breakpoint is not satisfied.
Although the size is required for all breakpoint types, an execute breakpoint is satisfied only if the address is the first byte in the instruction.
When you debug a multiprocessor system in kernel mode, breakpoints that you set by using bp (Set Breakpoint) or ba apply to all processors. For example, if the current processor is 3 and you type ba e1 MemoryAddress to put a breakpoint at MemoryAddress, any processor (not only processor 3) that executes at that address causes a breakpoint trap.
You cannot set the initial breakpoint in a user-mode process by using the ba command.
You cannot create multiple breakpoints at the same address that differ only in their CommandString values. However, you can create multiple breakpoints at the same address that have different restrictions (for example, different values of the /p, /t, /c, and /C options).
When you debug in kernel mode, the target computer distinguishes between user-mode and kernel-mode data breakpoints. A user-mode data breakpoint cannot affect kernel execution or memory access. A kernel-mode data breakpoint might affect user-mode execution or memory access, depending on whether the user-mode code is using the debug register state and whether there is a user-mode debugger that is attached.
To apply the current process' existing data breakpoints to a different register context, use the .apply_dbp (Apply Data Breakpoint to Context) command.
The following examples show the ba command. The following command sets a breakpoint for read access on 4 bytes of the variable myVar.
0:000> ba r4 myVar
The following command adds a breakpoint on all serial ports with addresses from 0x3F8 through 0x3FB. This breakpoint is triggered if anything is read or written to these ports.
kd> ba i4 3f8
Additional Information
For more information about and examples of using breakpoints, other breakpoint commands and methods of controlling breakpoints, and information about how to set breakpoints in user space from a kernel debugger, see Using Breakpoints. For more information about conditional breakpoints, see Setting a Conditional Breakpoint.
Build machine: CAPEBUILD
Additional Information
For more information about and examples of using breakpoints, other breakpoint commands and methods of controlling breakpoints, and information about how to set breakpoints in user space from a kernel debugger, see Using Breakpoints. For more information about conditional breakpoints, see Setting a Conditional Breakpoint.