The callee has to do the other side of the handshake I discussed last time. But it also has to do a little more housekeeping. There are two registers which point into the stack, esp points to the top of the stack and is kept up to date by push/pop. ebp is the base pointer and points to the bottom of the current stack frame. It must be set when we create a new stack frame, such as when we enter a function. Since ebp is a callee-preserved register we must save the old value of ebp to the stack and later restore it.
cdecl
void __declspec(naked) copy(...)
{
__asm {
//prologue
push ebp
mov ebp, esp
push esi
//copy
mov eax, [ebp+12]
mov esi, [ebp+8]
cmp eax, esi
je finish
mov ecx, [ebp+16]
start:
mov dl, BYTE PTR [esi+ecx]
mov BYTE PTR [eax+ecx], dl
loop start
mov dl, BYTE PTR [esi]
mov BYTE PTR [eax], dl
finish:
//epilogue
pop esi
pop ebp
ret
}
}
I've elided the arguments to the function (since we are doing the calling now, they are irrelevant). The __declspec(naked) declaration means that the compiler will not generate prologue and epilogue for the function. Our epilogue saves ebp to the stack, moves esp to ebp and saves esi to the stack. In the epilogue, esi is restored (it is the only register we need to preserve), then restore ebp. Because there are no push/pops in the body of the function, the stack pointer is correct. The body of the function is pretty much unchanged, the only difference is that we access the arguments by offset from ebp (i.e., in the stack), rather than by name.
Normally, we would store the arguments on the stack, somewhere between ebp and esp, but here we never take them out of the registers, so no need. We would often allocate memory on the stack (also between ebp and esp) for local variables, but we don't need to do that either.
fastcall
Here, we get the arguments in registers, we could copy them to the stack, but we don't need to, so we'll just use them. Because we don't pass anything on the stack, there is nothing to tidy up. We could avoid a couple of moves by using different registers, but I haven't.
//prologue
push ebp
mov ebp, esp
push edi
//fillWithCount
mov edi, ecx
mov ecx, edx
dec ecx
start:
mov BYTE PTR [edi+ecx], cl
loop start
mov BYTE PTR [edi], cl
//epilogue
pop edi
pop ebp
ret
stdcall
Here the arguments are on the stack, and we must restore it on exit.
//prologue
push ebp
mov ebp, esp
push edi
push ebx
//fill
mov edi, [ebp+8]
mov ebx, [ebp+12]
mov ecx, [ebp+16]
dec ecx
start:
mov BYTE PTR [edi+ecx], bl
loop start
mov BYTE PTR [edi], bl
//epilogue
pop ebx
pop edi
pop ebp
ret 12
We use the arguments in the same way as for the cdecl function. The difference is that the ret instruction takes an argument, the number of bytes to pop from the stack in order to tidy up. We could also do this manually:
pop edx
add esp, 12
jmp edx
I'm a research engineer at Mozilla working on the Rust compiler. I have history with Firefox layout and graphics, and programming language theory and type systems (mostly of the OO, Featherweight flavour, thus the title of the blog). http://www.ncameron.org @nick_r_cameron
Saturday, May 26, 2012
Monday, May 21, 2012
Assembly - calling conventions - callers
Next up calling conventions, what does C do when it calls a function? Actually there are a few ways of calling functions, called calling conventions, there are choices about whether the caller or callee must restore registers, how argument and the return value are passed, how this is passed if it's a class method (not to mention virtual dispatch, but that's beyond the scope of this blog post). I will look at the following common conventions: cdecl (standard convention for C on x86), stdcall (used for the Win32 API), and fastcall (there are actually many varieties, but I'll look at the Microsoft version). There are many other conventions on different platforms and for different languages. In particular, there is thiscall, which is used by Microsoft for calling member functions, and a bunch of conventions for 64 bit processors, which favour registers, because there are more of them (cite: http://msdn.microsoft.com/en-us/library/984x0h58.aspx).
We'll use the call and ret instructions. In all cases, call saves the return address to the stack and ret pops it, so the return address is always on top of the stack when a call is made.
I'm going to continue to use the functions from last time, but now I'm going to add a calling convention to the signature and call them from assembly, rather than C.
Here are the new signatures:
void __cdecl copy(unsigned char* aSrc, unsigned char* aDest, unsigned int aLength);
void __stdcall fill(unsigned char* aDest, unsigned char aVal, unsigned int aLength);
void __fastcall fillWithCount(unsigned char* aDest, unsigned int aLength);
And here is the current calling code in C (there is some allocation before hand, and some printing after, but that is not important):
fillWithCount(src, 256);
fill(dest, 5, 256);
copy(src, dest, 200);
cdecl
Arguments are passed on the stack in right to left order. Return value is in eax. eax, ecx, and edx must be preserved by the caller, other registers are preserved by the callee. The caller must clear the stack on return. So, the call to copy looks like:
//copy(src, dest, 200);
__asm {
push 200
push dest
push src
call copy
add esp, 12
}
Which is actually not very interesting because there is no return value, nor registers to restore. The only thing of note is that we pop the three params off the stack by manually fiddling with the stack pointer.
stdcall
stdcall is similar to cdecl, but the callee tidies up the stack. So we don't need to pop anything from the stack.
//fill(dest, 5, 256);
__asm {
push 256
push 5
push dest
call fill
}
fastcall
fastcall is similar to stdcall in that the callee tidies up the stack, but two parameters are passed in ecx and edx, the rest on the stack like the other conventions. The return value is left in eax.
//fillWithCount(src, 256);
__asm {
mov ecx, src
mov edx, 256
call fillWithCount
}
We'll use the call and ret instructions. In all cases, call saves the return address to the stack and ret pops it, so the return address is always on top of the stack when a call is made.
I'm going to continue to use the functions from last time, but now I'm going to add a calling convention to the signature and call them from assembly, rather than C.
Here are the new signatures:
void __cdecl copy(unsigned char* aSrc, unsigned char* aDest, unsigned int aLength);
void __stdcall fill(unsigned char* aDest, unsigned char aVal, unsigned int aLength);
void __fastcall fillWithCount(unsigned char* aDest, unsigned int aLength);
And here is the current calling code in C (there is some allocation before hand, and some printing after, but that is not important):
fillWithCount(src, 256);
fill(dest, 5, 256);
copy(src, dest, 200);
cdecl
Arguments are passed on the stack in right to left order. Return value is in eax. eax, ecx, and edx must be preserved by the caller, other registers are preserved by the callee. The caller must clear the stack on return. So, the call to copy looks like:
//copy(src, dest, 200);
__asm {
push 200
push dest
push src
call copy
add esp, 12
}
Which is actually not very interesting because there is no return value, nor registers to restore. The only thing of note is that we pop the three params off the stack by manually fiddling with the stack pointer.
stdcall
stdcall is similar to cdecl, but the callee tidies up the stack. So we don't need to pop anything from the stack.
//fill(dest, 5, 256);
__asm {
push 256
push 5
push dest
call fill
}
fastcall
fastcall is similar to stdcall in that the callee tidies up the stack, but two parameters are passed in ecx and edx, the rest on the stack like the other conventions. The return value is left in eax.
//fillWithCount(src, 256);
__asm {
mov ecx, src
mov edx, 256
call fillWithCount
}
Thursday, May 17, 2012
Assembly, day 4 - memory modes
'Today' I wanted to learn about the different addressing modes available in x86 assembly, so I wrote a few functions which fill an array of bytes in various ways.
The addressing modes are: a constant (e.g, 5), a C variable (I suppose this is some inline assembly magic; e.g., x), the contents of a register (e.g., eax), the value in memory pointed to by a register (e.g., [eax]), that plus a fixed offset (e.g., [eax+5]), or that plus a variable offset ([eax+ecx]).
void fill(unsigned char* aDest, unsigned char aVal, unsigned int aLength)
{
__asm {
mov edi, aDest
movzx ebx, aVal // because I can!
mov ecx, aLength
dec ecx
start:
mov BYTE PTR [edi+ecx], bl
loop start
mov BYTE PTR [edi], bl
}
}
This function fills the aLength bytes starting at *aDest with aVal. We use loop to do this aLength times (which we setup by moving aLength into ecx). It turns out that loop is one-indexed, so we have to decrement at the start of the loop and do the zero iteration manually. Is there a better way to do this?
Here we use the edi register plus ecx offset to index into memory and set it with the value in bl. Note we have to use the BYTE PTR directive to move only a single byte. When we set up bl, we actually use movzx to fill the upper three bytes of ebx with zeroes, this was just for fun, we don't need to do this.
void fillWithCount(unsigned char* aDest, unsigned int aLength)
{
__asm {
mov ecx, aLength
dec ecx
mov edi, aDest
start:
mov BYTE PTR [edi+ecx], cl
loop start
mov BYTE PTR [edi], cl
}
}
Will with count puts the number n into the aDest+nth address. It is pretty similar to the above function.
void copy(unsigned char* aSrc, unsigned char* aDest, unsigned int aLength)
{
__asm {
mov eax, aDest
mov esi, aSrc
cmp eax, esi
je finish
mov ecx, aLength
start:
mov dl, BYTE PTR [esi+ecx]
mov BYTE PTR [eax+ecx], dl
loop start
mov dl, BYTE PTR [esi]
mov BYTE PTR [eax], dl
finish:
}
}
This one is a bit more fun, we copy aLength bytes from aSrc to aDest. We first check that aSrc and aDest are different. An interesting aside: instructions involving eax, such as 'cmp eax, esi' are often optimised on x86, that line ought to get compiled into a single byte opcode with a single argument.
Then we loop through the data as in the previous functions. We have to do two moves here because x86 does not support memory to memory moves, so we move a byte to dl and then from there to the destination. I should probably have used eax rather than edx as the temporary, rather than using it to hold the destination address.
The addressing modes are: a constant (e.g, 5), a C variable (I suppose this is some inline assembly magic; e.g., x), the contents of a register (e.g., eax), the value in memory pointed to by a register (e.g., [eax]), that plus a fixed offset (e.g., [eax+5]), or that plus a variable offset ([eax+ecx]).
void fill(unsigned char* aDest, unsigned char aVal, unsigned int aLength)
{
__asm {
mov edi, aDest
movzx ebx, aVal // because I can!
mov ecx, aLength
dec ecx
start:
mov BYTE PTR [edi+ecx], bl
loop start
mov BYTE PTR [edi], bl
}
}
This function fills the aLength bytes starting at *aDest with aVal. We use loop to do this aLength times (which we setup by moving aLength into ecx). It turns out that loop is one-indexed, so we have to decrement at the start of the loop and do the zero iteration manually. Is there a better way to do this?
Here we use the edi register plus ecx offset to index into memory and set it with the value in bl. Note we have to use the BYTE PTR directive to move only a single byte. When we set up bl, we actually use movzx to fill the upper three bytes of ebx with zeroes, this was just for fun, we don't need to do this.
void fillWithCount(unsigned char* aDest, unsigned int aLength)
{
__asm {
mov ecx, aLength
dec ecx
mov edi, aDest
start:
mov BYTE PTR [edi+ecx], cl
loop start
mov BYTE PTR [edi], cl
}
}
Will with count puts the number n into the aDest+nth address. It is pretty similar to the above function.
void copy(unsigned char* aSrc, unsigned char* aDest, unsigned int aLength)
{
__asm {
mov eax, aDest
mov esi, aSrc
cmp eax, esi
je finish
mov ecx, aLength
start:
mov dl, BYTE PTR [esi+ecx]
mov BYTE PTR [eax+ecx], dl
loop start
mov dl, BYTE PTR [esi]
mov BYTE PTR [eax], dl
finish:
}
}
This one is a bit more fun, we copy aLength bytes from aSrc to aDest. We first check that aSrc and aDest are different. An interesting aside: instructions involving eax, such as 'cmp eax, esi' are often optimised on x86, that line ought to get compiled into a single byte opcode with a single argument.
Then we loop through the data as in the previous functions. We have to do two moves here because x86 does not support memory to memory moves, so we move a byte to dl and then from there to the destination. I should probably have used eax rather than edx as the temporary, rather than using it to hold the destination address.
Wednesday, May 16, 2012
Assembly - interlude
I just want to mention that I am really just learning this assembly stuff, and making it up as I go along. If anyone is experienced, and sees better ways to do things, I would really appreciate a comment, thanks!
Tuesday, May 15, 2012
Assembly, day 3 - more control flow
So to make things a bit more complicated, I wanted to simulate some basic parts of procedure call and recursion. I'm not going to try to interact with or emulate C or anything (later, later), I just want some more complex control flow which includes parameter passing and so forth. The example will be the Fibonacci series (of course).
First in C (this isn't nice code, but it is close to the simple assembly coming next):
int fib(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
int result = fib(n--);
result += fib(n--);
return result;
}
And then in assembly:
push done
mov ebx, n
start:
cmp ebx, 0
je case0
cmp ebx, 1
je case1
dec ebx
push ret1
jmp start
ret1:
push eax
push ret2
dec ebx
jmp start
ret2:
pop ecx
add eax, ecx
add ebx, 2
jmp fin
case0:
mov eax, 0
jmp fin
case1:
mov eax, 1
fin:
pop ecx
jmp ecx
done:
mov result, eax
We mostly only use two registers: ebx takes the argument to the function, and eax stores the result. The calling convention is that eax is preserved by the caller and ebx is preserved by the callee. ecx is used as a temporary. When a procedure is called, the argument is in ebx, the return address is on top of the stack and below that is the callee's business, the result is left in eax.
We start by 'calling' our procedure for the first time by pushing the final return address ('done') and moving the initial argument to ebx. Once we're done we'll end up at 'done:' because we just pushed that, there we will simply move the result from eax to 'result'.
'start:' is the entry point to the procedure, my invariant here is that the arg must be in ebx and the return address is on top of the stack. We check if the argument (ebx) is 0 or 1 and jump to the special cases, otherwise we continue. The special cases just leave 0 or 1 in eax and then execute the epilogue code (at 'fin:'), which simply pops the return address from the stack and jumps there (this should probably have been inlined).
So, the interesting case is where ebx > 1, here we decrement ebx push ret1 as the return address and recurse. When we come back we save eax (the result of the first call) to the stack, push ret2 as the return address, decrement ebx again and recurse again. On return we restore the old value of eax to ecx, and then add it to the new value of eax (which is the result of the second call). We then restore ebx by adding two (because we decremented twice) and go to the epilogue to return. Note that we have fulfilled the calling convention by restoring ebx and leaving the result in eax.
First in C (this isn't nice code, but it is close to the simple assembly coming next):
int fib(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
int result = fib(n--);
result += fib(n--);
return result;
}
And then in assembly:
push done
mov ebx, n
start:
cmp ebx, 0
je case0
cmp ebx, 1
je case1
dec ebx
push ret1
jmp start
ret1:
push eax
push ret2
dec ebx
jmp start
ret2:
pop ecx
add eax, ecx
add ebx, 2
jmp fin
case0:
mov eax, 0
jmp fin
case1:
mov eax, 1
fin:
pop ecx
jmp ecx
done:
mov result, eax
We mostly only use two registers: ebx takes the argument to the function, and eax stores the result. The calling convention is that eax is preserved by the caller and ebx is preserved by the callee. ecx is used as a temporary. When a procedure is called, the argument is in ebx, the return address is on top of the stack and below that is the callee's business, the result is left in eax.
We start by 'calling' our procedure for the first time by pushing the final return address ('done') and moving the initial argument to ebx. Once we're done we'll end up at 'done:' because we just pushed that, there we will simply move the result from eax to 'result'.
'start:' is the entry point to the procedure, my invariant here is that the arg must be in ebx and the return address is on top of the stack. We check if the argument (ebx) is 0 or 1 and jump to the special cases, otherwise we continue. The special cases just leave 0 or 1 in eax and then execute the epilogue code (at 'fin:'), which simply pops the return address from the stack and jumps there (this should probably have been inlined).
So, the interesting case is where ebx > 1, here we decrement ebx push ret1 as the return address and recurse. When we come back we save eax (the result of the first call) to the stack, push ret2 as the return address, decrement ebx again and recurse again. On return we restore the old value of eax to ecx, and then add it to the new value of eax (which is the result of the second call). We then restore ebx by adding two (because we decremented twice) and go to the epilogue to return. Note that we have fulfilled the calling convention by restoring ebx and leaving the result in eax.
Wednesday, May 09, 2012
Assembly, day 2 - simple control flow
Using the same inline assembly framework as yesterday, today I want to use a simple loop. The example will be to sum all the numbers between 0 and n, where n is passed in as a parameter. To do this I will simulate a 'backward' for loop: for (; n > 0; --n), and in the body just add n to the running total. The function looks like:
int sum(unsigned int n)
{
int result;
__asm {
mov eax, 0
mov ebx, n
start:
add eax, ebx
dec ebx
cmp ebx, 0
jg start
mov result, eax
}
return result;
}
Here, we keep the running total in eax, and the current value of n in ebx. The first two mov instructions initialise these registers. Then the 'start' label identifies the beginning of the loop. In the loop we add the current 'n' to the running result, and decrement 'n'. The 'cmp' instruction compares its two arguments and sets some flags accordingly, the following instruction (jg) uses those flags and jumps if ebx is greater than zero. If we don't jump, then we are done and we can copy the result out of the register and into memory, to 'result', to be precise.
An Alternative:
mov eax, 0
mov ecx, n
start:
add eax, ecx
loopnz start
mov result, eax
In the alternative we make use of the x86 loop instruction (the C stuff is exactly the same as the first version). Now we use ecx, rather than ebx, to store our counter ('n'). The loopnz instruction automatically decrements ecx (which is why we use it instead of ebx), and jumps to the given label if ecx is not equal to zero (n is always positive, so that is OK). This version uses fewer instructions, and is perhaps a little clearer. No idea which version would be quicker, modern processors are far too complex to make such a judgement without measuring.
int sum(unsigned int n)
{
int result;
__asm {
mov eax, 0
mov ebx, n
start:
add eax, ebx
dec ebx
cmp ebx, 0
jg start
mov result, eax
}
return result;
}
Here, we keep the running total in eax, and the current value of n in ebx. The first two mov instructions initialise these registers. Then the 'start' label identifies the beginning of the loop. In the loop we add the current 'n' to the running result, and decrement 'n'. The 'cmp' instruction compares its two arguments and sets some flags accordingly, the following instruction (jg) uses those flags and jumps if ebx is greater than zero. If we don't jump, then we are done and we can copy the result out of the register and into memory, to 'result', to be precise.
An Alternative:
mov eax, 0
mov ecx, n
start:
add eax, ecx
loopnz start
mov result, eax
In the alternative we make use of the x86 loop instruction (the C stuff is exactly the same as the first version). Now we use ecx, rather than ebx, to store our counter ('n'). The loopnz instruction automatically decrements ecx (which is why we use it instead of ebx), and jumps to the given label if ecx is not equal to zero (n is always positive, so that is OK). This version uses fewer instructions, and is perhaps a little clearer. No idea which version would be quicker, modern processors are far too complex to make such a judgement without measuring.
Tuesday, May 08, 2012
Inline assembly
A mini-quest of mine is to (re-)learn x86 assembly language. Today I tried to get a 'Hello World!' thing going. My chosen platform is inline assembly in MSVC, basically because it seems like the easiest way. And it was pretty easy. Since strings in assembly are pretty complex, my program will add two integers together.
Inline assembly is just put in a '__asm' block. And it is really simple. Variable names can just be used inside the block. So my whole program looks like:
int add(int x, int y)
{
__asm {
mov eax, y
add x, eax
}
return x;
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("sum: %d\n", add(4, 23));
return 0;
}
Where add is a c function who's implementation is in assembly (well the prelude and epilogue are done by the C++ compiler, but that's one reason to use inline assembly, told you it made things easy). The main function supplies the arguments and prints the result.
The actual assembly is really simple, 'add' adds its second argument to the first, only one of the arguments can be a memory location, so we have to use a register for the second argument. The 'mov' instruction copies the value in y to eax, which then gets added to x, which holds the result.
Inline assembly is just put in a '__asm' block. And it is really simple. Variable names can just be used inside the block. So my whole program looks like:
int add(int x, int y)
{
__asm {
mov eax, y
add x, eax
}
return x;
}
int _tmain(int argc, _TCHAR* argv[])
{
printf("sum: %d\n", add(4, 23));
return 0;
}
Where add is a c function who's implementation is in assembly (well the prelude and epilogue are done by the C++ compiler, but that's one reason to use inline assembly, told you it made things easy). The main function supplies the arguments and prints the result.
The actual assembly is really simple, 'add' adds its second argument to the first, only one of the arguments can be a memory location, so we have to use a register for the second argument. The 'mov' instruction copies the value in y to eax, which then gets added to x, which holds the result.
Monday, May 07, 2012
How a debugger works
Debuggers are part of a computer that seems like magic, I've often wondered how they work, so I set out to find out. I found out about the Windows user-mode debugger. Kernel-mode debugging is similar, but a bit more complicated. I imagine it is similar in different OSs, but I don't really know.
Your standard debugger runs in a separate process from the debuggee, it must have some special privileges over the debuggee process. When the debugged process is running, the debugger does nothing, and execution runs as usual.
Debugging on Windows basically works via windows exceptions, that is the structured exception handler (seh) mechanism. As an aside I never realised C++ exceptions were compiled to seh OS calls, I always assumed they just used jumps, no wonder they are expensive. Anyway, when the debugger attaches to the debugged process, it is registered with the OS (Windows) as the first-chance exception handler for the debuggee process. Any exceptions thrown by the debuggee are caught by the debugger, which can do with them as it pleases. Usually it will print a notification and pass the exception on to the debuggee. The debuggee can then run its usual exception handling procedure.
If the debuggee doesn't catch the exception, which would usually cause a crash (or at least revert to OS handling), then the debugger gets another go with the exception, called second-chance exception handling. Usually, the debugger will stop execution and let the user debug the 'crash'.
Another, longer, aside: this gets complicated if the debuggee has some kind of crash reporter for dealing with unhandled exceptions. In normal execution, any unhandled exception is handled by the unhandled exception filter, which can be set with a system call. But when debugging, this is always turned off, so the debuggee never gets its last chance exception handler called, instead the debugger catches it, as described above.
There are a special set of exceptions which are used only by the debugger. These are caught by the debugger process in the first-chance exception handler and not passed on to the debuggee process. Depending on the exception, the debugger takes some action.
If the debugger wants to break, then a new thread is created in the debuggee process which just executes the debug break CPU instruction (int 3, apparently). The OS handles this and throws one of those special exceptions (STATUS_BREAKPOINT), which is caught by the debugger. The debugger can then use its special debugging privileges to pause all the threads in the debuggee process, and voila: a break has happened and the debugger is in control.
Breakpoints work similarly, when you breakpoint a line of code, the debugger (presumably with some help from the compiler) finds the corresponding instruction and overwrites it with that 'int 3' instruction. When it gets executed (where the original instruction should be), the same thing happens as above. When the debugger is done, it overwrites the 'int 3' with the original instruction and control returns back to the debuggee. The debugger must single step through the instruction (see next) and control will come back to the debugger so that it can re-replace the instruction with 'int 3' again, then single stepping is stopped and control is properly returned to the debuggee.
To single step, a CPU flag is set which causes the 'int 1' interrupt to occur after every instruction, control follows the path of 'int 3', above. The debugger can return control to the debuggee to execute all instructions that correspond with a single C++ line (or whatever language and concept of 'single step' is being debugged). At the end of a step, execution is paused as for a break. When the debugger is done single stepping, the CPU flag is unset and control transferred back to the debuggee.
Your standard debugger runs in a separate process from the debuggee, it must have some special privileges over the debuggee process. When the debugged process is running, the debugger does nothing, and execution runs as usual.
Debugging on Windows basically works via windows exceptions, that is the structured exception handler (seh) mechanism. As an aside I never realised C++ exceptions were compiled to seh OS calls, I always assumed they just used jumps, no wonder they are expensive. Anyway, when the debugger attaches to the debugged process, it is registered with the OS (Windows) as the first-chance exception handler for the debuggee process. Any exceptions thrown by the debuggee are caught by the debugger, which can do with them as it pleases. Usually it will print a notification and pass the exception on to the debuggee. The debuggee can then run its usual exception handling procedure.
If the debuggee doesn't catch the exception, which would usually cause a crash (or at least revert to OS handling), then the debugger gets another go with the exception, called second-chance exception handling. Usually, the debugger will stop execution and let the user debug the 'crash'.
Another, longer, aside: this gets complicated if the debuggee has some kind of crash reporter for dealing with unhandled exceptions. In normal execution, any unhandled exception is handled by the unhandled exception filter, which can be set with a system call. But when debugging, this is always turned off, so the debuggee never gets its last chance exception handler called, instead the debugger catches it, as described above.
There are a special set of exceptions which are used only by the debugger. These are caught by the debugger process in the first-chance exception handler and not passed on to the debuggee process. Depending on the exception, the debugger takes some action.
If the debugger wants to break, then a new thread is created in the debuggee process which just executes the debug break CPU instruction (int 3, apparently). The OS handles this and throws one of those special exceptions (STATUS_BREAKPOINT), which is caught by the debugger. The debugger can then use its special debugging privileges to pause all the threads in the debuggee process, and voila: a break has happened and the debugger is in control.
Breakpoints work similarly, when you breakpoint a line of code, the debugger (presumably with some help from the compiler) finds the corresponding instruction and overwrites it with that 'int 3' instruction. When it gets executed (where the original instruction should be), the same thing happens as above. When the debugger is done, it overwrites the 'int 3' with the original instruction and control returns back to the debuggee. The debugger must single step through the instruction (see next) and control will come back to the debugger so that it can re-replace the instruction with 'int 3' again, then single stepping is stopped and control is properly returned to the debuggee.
To single step, a CPU flag is set which causes the 'int 1' interrupt to occur after every instruction, control follows the path of 'int 3', above. The debugger can return control to the debuggee to execute all instructions that correspond with a single C++ line (or whatever language and concept of 'single step' is being debugged). At the end of a step, execution is paused as for a break. When the debugger is done single stepping, the CPU flag is unset and control transferred back to the debuggee.
Wednesday, May 02, 2012
Quick post - a question about Tribal Ownership
Tribal Ownership is probably the work I am most proud of. The idea is that syntactic nesting of classes gives semantic nesting of objects, with very little extra syntax. (Note, the original idea is not mine, it was proposed in the original Tribe paper, and may have been known before).
A challenge for this kind of approach has occurred to me: a common case is that we want a tree where the children are owned by their parents, and each node has the same class. It is easy to have a tree where all nodes are owned by a single object, but I think the common situation where a parent owns its children is not possible. It would be good to have a solution to this, without changing the language too much. Or prove that we never really need this, and a tree where all nodes are owned by the root or a manager is OK in some situations (perhaps only some very limited applications of ownership). No ideas right now though, if you have an idea, leave a comment, or write it up and publish it!
A challenge for this kind of approach has occurred to me: a common case is that we want a tree where the children are owned by their parents, and each node has the same class. It is easy to have a tree where all nodes are owned by a single object, but I think the common situation where a parent owns its children is not possible. It would be good to have a solution to this, without changing the language too much. Or prove that we never really need this, and a tree where all nodes are owned by the root or a manager is OK in some situations (perhaps only some very limited applications of ownership). No ideas right now though, if you have an idea, leave a comment, or write it up and publish it!
Sunday, April 29, 2012
Android phone
I have recently acquired an Android smartphone - the HTC One X, to be precise. It is a very nice piece of kit indeed. It is my first smartphone (I know, and I call myself a geek). My wife has an iPhone, and I've played with various Android devices over the years, but it seems to me that the Android platform is finally coming into its own, at least, for those who want a more 'interesting' experience than the iPhone.
Obviously, the hardware is very nice - the screen is beautiful (super-high resolution and very nice colours), and everything is very snappy, quick, and very, very pretty (which means the graphics bits must be doing some hard work). The size (4.7 inches) is ideal, it is not so big as to cause thumb strain, but the screen is very 'visible', it feels a lot more usable for a lot more things than the iPhone or similar-size devices. The main camera is really good, but the flash is a bit green for close ups, the forward facing camera (for Skype etc.) has poorer colour balance and is a little fish eyed, but still better than a standard webcam.
The Android and toned-down-Sense UI work very well, so much less clunky than older versions of Android. Most of the UI is very easy to use and intuitive. I would go so far as to say it is a better UI than iOS, but maybe that is because I have a bit of a geeky mindset. All the basic utilities and OS things (app selection, home screen layout, clock, calender, and so forth) work very well, the balance of summary vs detail is nearly always just right. There is great consistency, the same gestures tend to do the same things, wherever you are, and they seem more easily discoverable than on the iPhone. Having three perma-buttons, rather than one is a win, I think, although having a menu button (like the Samsung phones) would probably be good, since all the apps have one, but often in different places. Actually the recent apps (kind of task switching) button could probably have been got rid of, I only use it rarely. The mail application could do with some work though: I want to delete an email with one click and without reading the message, and I want to see all my new mail (from multiple accounts) in one place, and I want more than one message on my lock screen.
The thing I really like (and which really differentiates it from the iPhone) is how everything is so customisable. Everything I have wanted to change so far, I have been able to (actually, except the home screen clock, if you want to keep the weather forecast with it - you can make it smaller, but are stuck with the calender-style digits, and the aforesaid mail-on-lock-screen thing). And, you can customise without ruining the look and feel, a very impressive balance has been struck!
On the downside, I guess the UI guidelines are not as strictly enforced across apps as on the iPhone, some apps should be a bit more standard.
On to the fun stuff; it was a bit of effort to get a debug link to the phone. Setting the phone up was easy, but getting it to talk to the laptop, not so much. There are a lot of options for connecting, and it is unclear which to use for development work. HTC don't make available the required drivers either, so you have to install their sync software, which includes the drivers, then sync (which tries to auto-run) conflicts with ADB, so getting ADB to work reliably was tricky, eventually I got it working, and managed to install a nightly version of Fennec (mobile Firefox), so the path now is clear (hopefully). I'm looking forward to getting a debug version of Fennec on the phone and working out how to debug Android FF, but that first requires building Android FF locally, which is no picnic (top tip, you can download the Android apk from the Try server instead). I also installed the NDK plugin for Eclipse, so all I need now is a little bit of time and I can hack up some Android apps - fun times!
So, they say smart phones live or die by their apps, and so far my experience of Android apps has been good. All the ones I've downloaded have been very polished, usable, and powerful. Just as much as the equivalent iPhone apps, if not more. Plus they all tend to be fairly customisable, just like the UI. In fact using the app on my phone is often a more attractive way of accessing a service than using the website on my laptop. If anyone has recommendations for apps I should be using, please leave a comment!
Of course the real selling point of Android over iPhone is the openness of the system vs. living in a walled garden. I can't imagine buying a phone I can't put my own programs on, it just seems ridiculous. But, so far, other than the nightly version of Firefox, all the apps I've installed have been through the Google store, so I guess the practical affects are minor.
And last, but not least, the web. Obviously the mobile web is a big thing right now, and especially for Mozilla. I found mobile web disappointing. I tried a whole bunch of browsers, and none of them were as well-adjusted for mobile as I would like, they all felt like they needed some tweaking. All had bugs too, unlike desktop where you rarely notice bugs in any modern browser. More importantly, websites need to have mobile versions. Desktop versions of websites just don't work very well on mobile, mostly the problem is screen size, but there are other things, slow internet connection and slow processor cause problems too. When websites do make mobile versions they, sometimes use redirecting, which is annoying, having to wait for another page load (even worse when they ask you first). Or the mobile site is designed for iPhones or devices with similar sized screens, so is actually too big on the One X. I'm sure it is possible to do this right, but very few websites manage it, and using apps is nearly always a better experience. I hope that this changes, but as it requires some intelligence and effort, I'm not hopeful.
Obviously, the hardware is very nice - the screen is beautiful (super-high resolution and very nice colours), and everything is very snappy, quick, and very, very pretty (which means the graphics bits must be doing some hard work). The size (4.7 inches) is ideal, it is not so big as to cause thumb strain, but the screen is very 'visible', it feels a lot more usable for a lot more things than the iPhone or similar-size devices. The main camera is really good, but the flash is a bit green for close ups, the forward facing camera (for Skype etc.) has poorer colour balance and is a little fish eyed, but still better than a standard webcam.
The Android and toned-down-Sense UI work very well, so much less clunky than older versions of Android. Most of the UI is very easy to use and intuitive. I would go so far as to say it is a better UI than iOS, but maybe that is because I have a bit of a geeky mindset. All the basic utilities and OS things (app selection, home screen layout, clock, calender, and so forth) work very well, the balance of summary vs detail is nearly always just right. There is great consistency, the same gestures tend to do the same things, wherever you are, and they seem more easily discoverable than on the iPhone. Having three perma-buttons, rather than one is a win, I think, although having a menu button (like the Samsung phones) would probably be good, since all the apps have one, but often in different places. Actually the recent apps (kind of task switching) button could probably have been got rid of, I only use it rarely. The mail application could do with some work though: I want to delete an email with one click and without reading the message, and I want to see all my new mail (from multiple accounts) in one place, and I want more than one message on my lock screen.
The thing I really like (and which really differentiates it from the iPhone) is how everything is so customisable. Everything I have wanted to change so far, I have been able to (actually, except the home screen clock, if you want to keep the weather forecast with it - you can make it smaller, but are stuck with the calender-style digits, and the aforesaid mail-on-lock-screen thing). And, you can customise without ruining the look and feel, a very impressive balance has been struck!
On the downside, I guess the UI guidelines are not as strictly enforced across apps as on the iPhone, some apps should be a bit more standard.
On to the fun stuff; it was a bit of effort to get a debug link to the phone. Setting the phone up was easy, but getting it to talk to the laptop, not so much. There are a lot of options for connecting, and it is unclear which to use for development work. HTC don't make available the required drivers either, so you have to install their sync software, which includes the drivers, then sync (which tries to auto-run) conflicts with ADB, so getting ADB to work reliably was tricky, eventually I got it working, and managed to install a nightly version of Fennec (mobile Firefox), so the path now is clear (hopefully). I'm looking forward to getting a debug version of Fennec on the phone and working out how to debug Android FF, but that first requires building Android FF locally, which is no picnic (top tip, you can download the Android apk from the Try server instead). I also installed the NDK plugin for Eclipse, so all I need now is a little bit of time and I can hack up some Android apps - fun times!
So, they say smart phones live or die by their apps, and so far my experience of Android apps has been good. All the ones I've downloaded have been very polished, usable, and powerful. Just as much as the equivalent iPhone apps, if not more. Plus they all tend to be fairly customisable, just like the UI. In fact using the app on my phone is often a more attractive way of accessing a service than using the website on my laptop. If anyone has recommendations for apps I should be using, please leave a comment!
Of course the real selling point of Android over iPhone is the openness of the system vs. living in a walled garden. I can't imagine buying a phone I can't put my own programs on, it just seems ridiculous. But, so far, other than the nightly version of Firefox, all the apps I've installed have been through the Google store, so I guess the practical affects are minor.
And last, but not least, the web. Obviously the mobile web is a big thing right now, and especially for Mozilla. I found mobile web disappointing. I tried a whole bunch of browsers, and none of them were as well-adjusted for mobile as I would like, they all felt like they needed some tweaking. All had bugs too, unlike desktop where you rarely notice bugs in any modern browser. More importantly, websites need to have mobile versions. Desktop versions of websites just don't work very well on mobile, mostly the problem is screen size, but there are other things, slow internet connection and slow processor cause problems too. When websites do make mobile versions they, sometimes use redirecting, which is annoying, having to wait for another page load (even worse when they ask you first). Or the mobile site is designed for iPhones or devices with similar sized screens, so is actually too big on the One X. I'm sure it is possible to do this right, but very few websites manage it, and using apps is nearly always a better experience. I hope that this changes, but as it requires some intelligence and effort, I'm not hopeful.
Subscribe to:
Posts (Atom)