Automatic code insertion in tasm - c

I am trying to write a simple C startup to produce DOS COM file.
I have not yet written argc, argv implementations.
As a test, I'm trying to put dummy argv[0], and argc=1.
But I get 2 unexpected lines as in the dump :
;---------------------------------------------------------------
Turbo Debugger Log
CPU 80486
cs:0100B85902 mov ax,0259
cs:0103 50 push ax
cs:0104 BF5002 mov di,0250
cs:0107 FF35 push word ptr [di]
cs:0109 90 nop ; << Unexpected 1
cs:010A 0E push cs ; << Unexpected 2
cs:010B E83C00 call 014A
cs:010E E80000 call 0111
cs:0111 B44C mov ah,4C
cs:0113 CD21 int 21
cs:0115 55 push bp
cs:0116 8BEC mov bp,sp
cs:0118 8B4604 mov ax,[bp+04]
cs:011B 50 push ax
cs:011C B40E mov ah,0E
cs:011E CD10 int 10
cs:0120 58 pop ax
cs:0121 5D pop bp
cs:0122 C3 ret
cs:0123 50 push ax
cs:0124 55 push bp
cs:0125 8BEC mov bp,sp
cs:0127 C746026900 mov word ptr [bp+02],0069
cs:012C 5D pop bp
cs:012D E8E5FF call 0115
cs:0130 59 pop cx
cs:0131 C3 ret
cs:0132 55 push bp
cs:0133 8BEC mov bp,sp
cs:0135 56 push si
cs:0136 33F6 xor si,si
cs:0138 EB01 jmp 013B
cs:013A 46 inc si
cs:013B 8B5E04 mov bx,[bp+04]
Terminated, exit code 7
Stack :
5B7C:0028 FFFF
5B7C:0026 FFFF
5B7C:0024 FFFF
5B7C:0022 FFFF
5B7C:0020 FFFF
5B7C:001E FFFF
5B7C:001C FFFF
5B7C:001A FFFF
5B7C:0018 FFFF
5B7C:0016 0DEE
5B7C:0014 1C80
5B7C:0012 0280
5B7C:0010 2225
5B7C:000E 01AE
5B7C:000C 2225
5B7C:000A 01E4
5B7C:0008 F01D
5B7C:0006 FEF0
5B7C:0004 9A00
5B7C:0002 9FFF
5B7C:0000 20CD
5B7C:FFFE 0000
5B7C:FFFC 0259 ; << argv
5B7C:FFFA 0001 ; << argc
5B7C:FFF8 5B7C ; << Unexpected CS
5B7C:FFF6 0111
5B7C:FFF4 3206
5B7C:FFF2 5B7C
5B7C:FFF0 0115
5B7C:FFEE 3206
;---------------------------------------------------------------
I could not understand why these lines are added by the assembler.
Could segment-padding be the cause? Also, could there be a stack-frame 'like' construct?
Please enlighten me on this.
Here's the source :
;---------------------------------------------------------------
_TEXT SEGMENT WORD PUBLIC USE16 'CODE'
_TEXT ENDS
_DATA SEGMENT WORD PUBLIC USE16 'DATA'
_DATA ENDS
DGROUP GROUP _DATA , _TEXT
_TEXT SEGMENT
assume cs: _TEXT, ds: _DATA, ss: nothing, es: nothing
ORG 0100h
;----------------------------------
; Program Starts Here...
;----------------------------------
start:
; Push arguments in reverse order...
;====================================
mov ax, offset DGROUP:argv
push ax
mov di, offset DGROUP:argc
push word ptr [di]
call far ptr _main
call near ptr __terminate
;----------------------------------
;----------------------------------
; Function Declarations...
;----------------------------------
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
__terminate proc DIST
PUBLIC __terminate
mov ah, 4ch
int 21h
__terminate endp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;----------------------------------
_TEXT ends
_DATA SEGMENT
argc dw 1
arg0 db 'NEW_C0', 0h
argv dw DGROUP:arg0
_DATA ENDS
extrn _main
end start
;---------------------------------------------------------------

From a document I found titled "Turbo Assembler 2.0 New Features":
The default condition is SMART enabled. When SMART is enabled, a qualifying
FAR jump will be replaced by a NEAR or a SHORT jump. Also, when SMART is
enabled, a qualifying FAR call will be replaced by a PUSH CS instruction
and a NEAR call.
So TASM, being smart at recognizing that _main and your startup code are in the same segment (you are producing a COM file, after all), takes the liberty to replace:
call far ptr _main
by the shorter and faster near call:
push cs
call _main
The push is necessary because TASM must assume that main itself was compiled with a far return; it expects a 32-bit return address on the stack.
You should ask yourself whether you actually want/need this far call in a COM file.
As for the nop, I have no idea. It could be word padding in an effort to make the far return more efficient, but I find Jester's explanation (reserve space for a true far call) very plausible too.

Related

Small model DOS .exe compiled and linked by OpenWatcom crashes

I'm trying to create a small DOS .exe program. I wrote the entry point in NASM assembly
; st.nasm
global _small_code_
global _printmsg_
extern _main0_
segment code
_small_code_:
..start:
mov ax, data
mov ds, ax
mov ax, stack
mov ss, ax
mov sp, stacktop
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg
int 0x21
call _main0_
; call _printmsg_
; mov ax, 3
mov dx, ax
add dx, hello_msg
mov ah, 9 ; WRITE_STDOUT
int 0x21
mov ah, 0x4c ; EXIT, exit code in al
int 0x21
_printmsg_:
ret
push dx
xchg ax, dx
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg ; !!
int 0x21
pop dx
ret ; !! restore AX?
segment data
hello_msg: db 'Hello, World!', 13, 10, '$'
segment stack stack
resb 1024
stacktop:
Please note that I'm not sure how the ..start: code and the segments should look like, I copy-pasted that part from somewhere.
I wrote the main program in C:
/* prog.c */
void _printmsg(const char *msg);
int add(int a, int b) {
return a + b * 2;
}
void other() {
_printmsg("Hello!\r\n$"); /*CRASH*/
/*_printmsg(0);*/ /*OK*/
}
int _main0() {
return 5;
}
I compile it with this:
$ nasm -f obj -o st.obj st.nasm
$ owcc -bdos -mcmodel=s -fno-stack-check -Os -s -march=i86 -o prog.exe prog.c st.obj
The resulting prog.exe is:
$ xxd prog.exe
00000000: 4d5a 8b00 0100 0200 0300 4000 ffff 0100 MZ........#.....
00000010: 4b04 0000 0a00 0100 2000 0000 0000 0000 K....... .......
00000020: 0b00 0100 1000 0100 0000 0000 0000 0000 ................
00000030: d1e2 01d0 c3b8 0000 e934 00b8 0500 c300 .........4......
00000040: 4865 6c6c 6f21 0d0a 2400 b801 008e d8b8 Hello!..$.......
00000050: 0100 8ed0 bc4b 04b4 09ba 3b00 cd21 e8da .....K....;..!..
00000060: ff89 c281 c23b 00b4 09cd 21b4 4ccd 21c3 .....;....!.L.!.
00000070: 5292 b409 ba3b 00cd 215a c348 656c 6c6f R....;..!Z.Hello
00000080: 2c20 576f 726c 6421 0d0a 24 , World!..$
Disassembly of prog.exe:
$ ndisasm -e 0x20 -b 16 prog.exe
00000000 0B00 or ax,[bx+si]
00000002 0100 add [bx+si],ax
00000004 1000 adc [bx+si],al
00000006 0100 add [bx+si],ax
00000008 0000 add [bx+si],al
0000000A 0000 add [bx+si],al
0000000C 0000 add [bx+si],al
0000000E 0000 add [bx+si],al
00000010 D1E2 shl dx,1
00000012 01D0 add ax,dx
00000014 C3 ret
00000015 B80000 mov ax,0x0
00000018 E93400 jmp 0x4f
0000001B B80500 mov ax,0x5
0000001E C3 ret
0000001F 004865 add [bx+si+0x65],cl
00000022 6C insb
00000023 6C insb
00000024 6F outsw
00000025 210D and [di],cx
00000027 0A24 or ah,[si]
00000029 00B80100 add [bx+si+0x1],bh
0000002D 8ED8 mov ds,ax
0000002F B80100 mov ax,0x1
00000032 8ED0 mov ss,ax
00000034 BC4B04 mov sp,0x44b
00000037 B409 mov ah,0x9
00000039 BA3B00 mov dx,0x3b
0000003C CD21 int 0x21
0000003E E8DAFF call 0x1b
00000041 89C2 mov dx,ax
00000043 81C23B00 add dx,0x3b
00000047 B409 mov ah,0x9
00000049 CD21 int 0x21
0000004B B44C mov ah,0x4c
0000004D CD21 int 0x21
0000004F C3 ret
00000050 52 push dx
00000051 92 xchg ax,dx
00000052 B409 mov ah,0x9
00000054 BA3B00 mov dx,0x3b
00000057 CD21 int 0x21
00000059 5A pop dx
0000005A C3 ret
0000005B 48 dec ax
0000005C 656C gs insb
0000005E 6C insb
0000005F 6F outsw
00000060 2C20 sub al,0x20
00000062 57 push di
00000063 6F outsw
00000064 726C jc 0xd2
00000066 64210D and [fs:di],cx
00000069 0A24 or ah,[si]
prog.exe puts DOSBox to an infinite loop. Oddly enough, if I remove the string literal from the C source file (in the other function, which isn't even called), it successfully returns. What's wrong in the assembly file?
Please note that this is the first time I'm using OpenWatcom, and this is the first time I build a DOS .exe file.
I don't want to write a main function, because that would cause the OpenWatcom libc to be linked to the output executable, making it unnecessarily large.
The primary problem is in how you define the code segment. The Watcom C/C++ compiler when using the SMALL memory model requires the code segment to be called _TEXT with a class of CODE. This mismatch between the assembly code and the C code leads to the code segment being in different physical segments and the call _main0_ jumping to the wrong place in memory causing exceptions to be thrown and the program hanging or crashing.
You can also get the Watcom linker to generate the required STACK in the DOS EXE by creating a segment called _STACK with attribute STACK and class STACK. If you create the stack segment this way, you won't need to initialize SS:SP at the beginning of your program.
The other sections that Watcom uses in the SMALL memory model are:
_DATA segment with a class of DATA for read/write data
CONST segment with a class of DATA for string literals (that aren't expected to be modified)
CONST2 segment with a class of DATA for other read only data
_BSS segment with class of BSS for uninitialized data.
Watcom expects that the segments CONST, CONST2, _DATA and _BSS to all be in the same group called DGROUP. All the data in the same group can be referenced by the name of the group. When you set up DGROUP in the way Watcom expects then all you have to do is initialize DS to the DGROUP segment and not the individual segments within the group.
The special label that starts with .. acts as the DOS entry point where execution should start. Thus ..start is used to generate an entry point in the DOS EXE header telling the program loader where to start executing when the program is loaded into memory.
A revised version of your assembly code could have looked like this:
; st.nasm
; DGROUP in watcom C/C++ for small model is:
GROUP DGROUP CONST CONST2 _DATA _BSS
global _small_code_
global _printmsg_
extern _main0_
; Code Segment (16-bit code)
segment _TEXT use16 class=CODE
_small_code_:
; .. denotes the label to be used as the DOS entry point
..start:
mov ax, DGROUP
mov ds, ax
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg
int 0x21
call _main0_
; call _printmsg_
; mov ax, 3
mov dx, ax
add dx, hello_msg
mov ah, 9 ; WRITE_STDOUT
int 0x21
mov ah, 0x4c ; EXIT, exit code in al
int 0x21
_printmsg_:
ret
push dx
xchg ax, dx
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg ; !!
int 0x21
pop dx
ret ; !! restore AX?
; Read only string literals here
segment CONST class=DATA
hello_msg: db 'Hello, World!', 13, 10, '$'
; Other read only data here
segment CONST2 class=DATA
; Read/Write data here
segment _DATA class=DATA
; Uninitialized data segment
segment _BSS class=BSS
; Stack segment 1k in size
segment _STACK STACK class=STACK
resb 1024
This code assumes that SS != DS, however it would have to be compiled with OWCC's option -Wc,-zu that passes the -zu to WCC (Watcom Compiler). -zu modifies code generation so that:
-zu SS != DGROUP (i.e., do not assume stack is in data segment)
If you wish to set SS==DS==DGROUP there are a number of ways to do it. One option I may suggest is putting _STACK in DGROUP with all the other program data. You would need a label after resb 1024 like stack_top: so you can load that offset into SP at startup after you set SS to the same value as DS. This change would result in assembly code that looks like:
; st.nasm
; DGROUP in watcom C/C++ for small model is:
GROUP DGROUP CONST CONST2 _DATA _BSS _STACK
; _STACK has been added to DGROUP so we can set SS==DS==DGROUP
global _small_code_
global _printmsg_
extern _main0_
; Code Segment (16-bit code)
segment _TEXT use16 class=CODE
_small_code_:
; .. denotes the label to be used as the DOS entry point
..start:
mov ax, DGROUP
mov ds, ax
mov ss, ax ; Set stack SS:SP to DGROUP:stack_top
mov sp, stack_top
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg
int 0x21
call _main0_
; call _printmsg_
; mov ax, 3
mov dx, ax
add dx, hello_msg
mov ah, 9 ; WRITE_STDOUT
int 0x21
mov ah, 0x4c ; EXIT, exit code in al
int 0x21
_printmsg_:
ret
push dx
xchg ax, dx
mov ah, 9 ; WRITE_STDOUT
mov dx, hello_msg ; !!
int 0x21
pop dx
ret ; !! restore AX?
; Read/Write data here
segment _DATA class=DATA
; Read only string literals here
segment CONST class=DATA
hello_msg: db 'Hello, World!', 13, 10, '$'
; Other read only data here
segment CONST2 class=DATA
; Uninitialized data segment
segment _BSS class=BSS
; Stack segment 1k in size
segment _STACK STACK class=STACK
resb 1024
stack_top:

Difficulty understanding logic in disassembled binary bomb phase 3

I have the following assembly program from the binary-bomb lab. The goal is to determine the keyword needed to run the binary without triggering the explode_bomb function. I commented my analysis of the assembly for this program but I am having trouble piecing everything together.
I believe I have all the information I need, but I still am unable to see the actual underlying logic and thus I am stuck. I would greatly appreciate any help!
The following is the disassembled program itself:
0x08048c3c <+0>: push %edi
0x08048c3d <+1>: push %esi
0x08048c3e <+2>: sub $0x14,%esp
0x08048c41 <+5>: movl $0x804a388,(%esp)
0x08048c48 <+12>: call 0x80490ab <string_length>
0x08048c4d <+17>: add $0x1,%eax
0x08048c50 <+20>: mov %eax,(%esp)
0x08048c53 <+23>: call 0x8048800 <malloc#plt>
0x08048c58 <+28>: mov $0x804a388,%esi
0x08048c5d <+33>: mov $0x13,%ecx
0x08048c62 <+38>: mov %eax,%edi
0x08048c64 <+40>: rep movsl %ds:(%esi),%es:(%edi)
0x08048c66 <+42>: movzwl (%esi),%edx
0x08048c69 <+45>: mov %dx,(%edi)
0x08048c6c <+48>: movzbl 0x11(%eax),%edx
0x08048c70 <+52>: mov %dl,0x10(%eax)
0x08048c73 <+55>: mov %eax,0x4(%esp)
0x08048c77 <+59>: mov 0x20(%esp),%eax
0x08048c7b <+63>: mov %eax,(%esp)
0x08048c7e <+66>: call 0x80490ca <strings_not_equal>
0x08048c83 <+71>: test %eax,%eax
0x08048c85 <+73>: je 0x8048c8c <phase_3+80>
0x08048c87 <+75>: call 0x8049363 <explode_bomb>
0x08048c8c <+80>: add $0x14,%esp
0x08048c8f <+83>: pop %esi
0x08048c90 <+84>: pop %edi
0x08048c91 <+85>: ret
The following block contains my analysis
5 <phase_3>
6 0x08048c3c <+0>: push %edi // push value in edi to stack
7 0x08048c3d <+1>: push %esi // push value of esi to stack
8 0x08048c3e <+2>: sub $0x14,%esp // grow stack by 0x14 (move stack ptr -0x14 bytes)
9
10 0x08048c41 <+5>: movl $0x804a388,(%esp) // put 0x804a388 into loc esp points to
11
12 0x08048c48 <+12>: call 0x80490ab <string_length> // check string length, store in eax
13 0x08048c4d <+17>: add $0x1,%eax // increment val in eax by 0x1 (str len + 1)
14 // at this point, eax = str_len + 1 = 77 + 1 = 78
15
16 0x08048c50 <+20>: mov %eax,(%esp) // get val in eax and put in loc on stack
17 //**** at this point, 0x804a388 should have a value of 78? ****
18
19 0x08048c53 <+23>: call 0x8048800 <malloc#plt> // malloc --> base ptr in eax
20
21 0x08048c58 <+28>: mov $0x804a388,%esi // 0x804a388 in esi
22 0x08048c5d <+33>: mov $0x13,%ecx // put 0x13 in ecx (counter register)
23 0x08048c62 <+38>: mov %eax,%edi // put val in eax into edi
24 0x08048c64 <+40>: rep movsl %ds:(%esi),%es:(%edi) // repeat 0x13 (19) times
25 // **** populate malloced memory with first 19 (edit: 76) chars of string at 0x804a388 (this string is 77 characters long)? ****
26
27 0x08048c66 <+42>: movzwl (%esi),%edx // put val in loc esi points to into edx
***** // at this point, edx should contain the string at 0x804a388?
28
29 0x08048c69 <+45>: mov %dx,(%edi) // put val in dx to loc edi points to
***** // not sure what effect this has or what is in edi at this point
30 0x08048c6c <+48>: movzbl 0x11(%eax),%edx // edx = [eax + 0x11]
31 0x08048c70 <+52>: mov %dl,0x10(%eax) // [eax + 0x10] = dl
32 0x08048c73 <+55>: mov %eax,0x4(%esp) // [esp + 0x4] = eax
33 0x08048c77 <+59>: mov 0x20(%esp),%eax // eax = [esp + 0x20]
34 0x08048c7b <+63>: mov %eax,(%esp) // put val in eax into loc esp points to
***** // not sure what effect these movs have
35
36 // edi --> first arg
37 // esi --> second arg
38 // compare value in esi to edi
39 0x08048c7e <+66>: call 0x80490ca <strings_not_equal> // store result in eax
40 0x08048c83 <+71>: test %eax,%eax
41 0x08048c85 <+73>: je 0x8048c8c <phase_3+80>
42 0x08048c87 <+75>: call 0x8049363 <explode_bomb>
43 0x08048c8c <+80>: add $0x14,%esp
44 0x08048c8f <+83>: pop %esi
45 0x08048c90 <+84>: pop %edi
46 0x08048c91 <+85>: ret
Update:
Upon inspecting the registers before strings_not_equal is called, I get the following:
eax 0x804d8aa 134535338
ecx 0x0 0
edx 0x76 118
ebx 0xffffd354 -11436
esp 0xffffd280 0xffffd280
ebp 0xffffd2b8 0xffffd2b8
esi 0x804a3d4 134521812
edi 0x804f744 134543172
eip 0x8048c7b 0x8048c7b <phase_3+63>
eflags 0x282 [ SF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
and I get the following disassembled pseudocode using Hopper:
I even tried using both the number found in eax and the string seen earlier as my keyword but neither of them worked.
The function makes a modified copy of a string from static storage, into a malloced buffer.
This looks weird. The malloc size is dependent on strlen+1, but the memcpy size is a compile-time constant? Your decompilation apparently shows that address was a string literal so it seems that's fine.
Probably that missed optimization happened because of a custom string_length() function that was maybe only defined in another .c (and the bomb was compiled without link-time optimization for cross-file inlining). So size_t len = string_length("some string literal"); is not a compile-time constant and the compiler emitted a call to it instead of being able to use the known constant length of the string.
But probably they used strcpy in the source and the compiler did inline that as a rep movs. Since it's apparently copying from a string literal, the length is a compile-time constant and it can optimize away that part of the work that strcpy normally has to do. Normally if you've already calculated the length it's better to use memcpy instead of making strcpy calculate it again on the fly, but in this case it actually helped the compiler make better code for that part than if they'd passed the return value of string_length to a memcpy, again because string_length couldn't inline and optimize away.
<+0>: push %edi // push value in edi to stack
<+1>: push %esi // push value of esi to stack
<+2>: sub $0x14,%esp // grow stack by 0x14 (move stack ptr -0x14 bytes)
Comments like that are redundant; the instruction itself already says that. This is saving two call-preserved registers so the function can use them internally and restore them later.
Your comment on the sub is better; yes, grow the stack is the higher level semantic meaning here. This function reserves some space for locals (and for function args to be stored with mov instead of pushed).
The rep movsd copies 0x13 * 4 bytes, incrementing ESI and EDI to point past the end of the copied region. So another movsd instruction would copy another 4 bytes contiguous with the previous copy.
The code actually copies another 2, but instead of using movsw, it uses a movzw word load and a mov store. This makes a total of 78 bytes copied.
...
# at this point EAX = malloc return value which I'll call buf
<+28>: mov $0x804a388,%esi # copy src = a string literal in .rodata?
<+33>: mov $0x13,%ecx
<+38>: mov %eax,%edi # copy dst = buf
<+40>: rep movsl %ds:(%esi),%es:(%edi) # memcpy 76 bytes and advance ESI, EDI
<+42>: movzwl (%esi),%edx
<+45>: mov %dx,(%edi) # copy another 2 bytes (not moving ESI or EDI)
# final effect: 78-byte memcpy
On some (but not all) CPUs it would have been efficient to just use rep movsb or rep movsw with appropriate counts, but that's not what the compiler chose in this case. movzx aka AT&T movz is a good way to do narrow loads without partial-register penalties. That's why compilers do it, so they can write a full register even though they're only going to read the low 8 or 16 bits of that reg with a store instruction.
After that copy of a string literal into buf, we have a byte load/store that copies a character with buf. Remember at this point EAX is still pointing at buf, the malloc return value. So it's making a modified copy of the string literal.
<+48>: movzbl 0x11(%eax),%edx
<+52>: mov %dl,0x10(%eax) # buf[16] = buf[17]
Perhaps if the source hadn't defeated constant-propagation, with high enough optimization level the compiler might have just put the final string into .rodata where you could find it, trivializing this bomb phase. :P
Then it stores pointers as stack args for string compare.
<+55>: mov %eax,0x4(%esp) # 2nd arg slot = EAX = buf
<+59>: mov 0x20(%esp),%eax # function arg = user input?
<+63>: mov %eax,(%esp) # first arg slot = our incoming stack arg
<+66>: call 0x80490ca <strings_not_equal>
How to "cheat": looking at the runtime result with GDB
Some bomb labs only let you run the bomb online, on a test server, which would record explosions. You couldn't run it under GDB, only use static disassembly (like objdump -drwC -Mintel). So the test server could record how many failed attempts you had. e.g. like CS 3330 at cs.virginia.edu that I found with google, where full credit requires less than 20 explosions.
Using GDB to examine memory / registers part way through a function makes this vastly easier than only working from static analysis, in fact trivializing this function where the single input is only checked at the very end. e.g. just look at what other arg is being passed to strings_not_equal. (Especially if you use GDB's jump or set $pc = ... commands to skip past the bomb explosion checks.)
Set a breakpoint or single-step to just before the call to strings_not_equal. Use p (char*)$eax to treat EAX as a char* and show you the (0-terminated) C string starting at that address. At that point EAX holds the address of the buffer, as you can see from the store to the stack.
Copy/paste that string result and you're done.
Other phases with multiple numeric inputs typically aren't this easy to cheese with a debugger and do require at least some math, but linked-list phases that requires you to have a sequence of numbers in the right order for list traversal also become trivial if you know how to use a debugger to set registers to make compares succeed as you get to them.
rep movsl copies 32-bit longwords from address %esi to address %edi, incrementing both by 4 each time, a number of times equal to %ecx. Think of it as memcpy(edi, esi, ecx*4).
See https://felixcloutier.com/x86/movs:movsb:movsw:movsd:movsq (it's movsd in Intel notation).
So this is copying 19*4=76 bytes.

Initialize char[] fails, esi contains wrong value

I want to initialize a char array, but during I do this my programm crashes. Here's my code:
void kernelEnteredMsg() {
char str[] = "Kernel successfully entered!";
}
Here's the disassembly:
push ebp
mov ebp,esp
push edi
push esi
push ebx
sub esp,byte +0x30
lea edx,[ebp-0x2d]
mov ebx,0x402000 ; load an address outside my data segment
mov eax,0x1d
mov edi,edx
mov esi,ebx ; move this address to edi
mov ecx,eax
rep movsb ; here the programm crashes
add esp,byte +0x30
pop ebx
pop esi
pop edi
pop ebp
ret
I don't understand why it loads esi with 0x402000. But this seems to cause the error. Can somebody explain what happens here and how to fix it?
PS: "Kernel successful entered!" is at 0x1000 in binary file.
C code:
void kernelEnteredMsg();
void entryPoint() {
kernelEnteredMsg();
}
void kernelEnteredMsg() {
char str[] = "Kernel successfully entered!";
int size = 28;
}
Calling assembly code:
extern _entryPoint
global _main
section .text
_main: ; start of kernel
nop
; setup ds, es, ss and gs
mov ax, 16
mov ds, ax
mov es, ax
mov ss, ax
mov sp, 0x4000
mov ax, 24
mov gs, ax
mov [gs:0], dword 0x07690748 ; test graphics
call _entryPoint ; enter kernel C code
jmp $
This code does copy the string from the .text section to the local stack, because the char array is not 'const'. This may provide a simple solution if you do not need the string to be modified - just make it const char.
I don't understand why it loads esi with 0x402000.
ESI is the source of the string copy instruction 'rep movsb', EDI is the destination.
The address is constructed by IMAGE_BASE+SECTION (IIRC) in the PE file(assuming it is PE.)
Remember in the file there is a FILE_ALIGN and a SECTION_VIRTUAL_ADDRESS, so a section may be
at position 0x1000 in the file(FILE_ALIGN) and at 0x2000 in memory(VIRTUAL_ADDRESS) resulting in IMAGE_BASE+VIRTUAL_ADDRESS=0x402000.
You can use a PE explorer like CFF Explorer(http://www.ntcore.com/exsuite.php)
to display this(if it's a .bin file it may be unapplicable but it has to have some kind of format)
Another possibility may be a wrong state of the DF-Flag leading to wrong behaviour of the string copy instruction (should not happen, because the compiler should take care of this).
Try inserting
__asm__ ("cld");
before the char str[] or in the __main procedure to set string increment to 'UP'.

How Do I Access Thread Local Storage From ml64.exe (MSVC 64-bit X64 Assembler)?

The following C function attempts to prevent recursion in multicore code in a thread-safe manner using a thread local storage variable. However, for reasons that are somewhat complicated, I NEED to write this function in X64 assembler (Intel X86 / AMD 64-bit) and assemble it with ml64.exe from VC2010. I know how to do this if I'm using global variables but I'm not sure how to do it properly with a TLS variable that has __declspec(thread).
__declspec(thread) int tls_VAR = 0;
void norecurse( )
{
if(0==tls_VAR)
{
tls_VAR=1;
DoWork();
tls_VAR=0;
}
}
Note: This is what VC2010 kicks out for the function. However, MASM (ml64.exe) doesn't support the gs:88 or OFFSET FLAT: parts of the code.
; Listing generated by Microsoft (R) Optimizing Compiler Version 16.00.40219.01
include listing.inc
INCLUDELIB MSVCRTD
INCLUDELIB OLDNAMES
PUBLIC norecurse
EXTRN DoWork:PROC
EXTRN tls_VAR:DWORD
EXTRN _tls_index:DWORD
pdata SEGMENT
$pdata$norecurse DD imagerel $LN4
DD imagerel $LN4+70
DD imagerel $unwind$norecurse
pdata ENDS
xdata SEGMENT
$unwind$norecurse DD 040a01H
DD 06340aH
DD 07006320aH
; Function compile flags: /Ogtpy
xdata ENDS
_TEXT SEGMENT
norecurse PROC
; File p:\hackytests\64bittest2010\64bittest\64bittest.cpp
; Line 19
$LN4:
mov QWORD PTR [rsp+8], rbx
push rdi
sub rsp, 32 ; 00000020H
; Line 20
mov ecx, DWORD PTR _tls_index
mov rax, QWORD PTR gs:88
mov edi, OFFSET FLAT:tls_VAR
mov rbx, QWORD PTR [rax+rcx*8]
cmp DWORD PTR [rbx+rdi], 0
jne SHORT $LN1#norecurse
; Line 22
mov DWORD PTR [rbx+rdi], 1
; Line 23
call DoWork
; Line 24
mov DWORD PTR [rbx+rdi], 0
$LN1#norecurse:
; Line 26
mov rbx, QWORD PTR [rsp+48]
add rsp, 32 ; 00000020H
pop rdi
ret 0
norecurse ENDP
_TEXT ENDS
END
As your answer indicates the problem comes down finding the MASM equivalents to the following two lines in assembly listing generated by the Microsoft's C++ compiler:
mov rax, QWORD PTR gs:88
mov edi, OFFSET FLAT:tls_VAR
The first line is easy. Just replace gs:88 with gs:[88].
The second line is less obvious. The OFFSET FLAT: operator is a red herring. It means use the offset relative to the beginning of the "FLAT" segment. With the 32-bit version of MASM, the FLAT segment is the segment that includes the entire 4G address space. This is the segment that's used for both the code and data segment as part of the 32-bit flat memory model. The 64-bit version of MASM doesn't support memory models, it essentially always assumes a 64-bit version of the flat memory model, so it doesn't support the FLAT keyword. As result the plain OFFSET operator ends meaning the same thing. (In fact with the 32-bit assembler, plain OFFSET also normally means the same thing because PECOFF only supports the flat memory model.)
However using OFFSET here won't work. That's because it would use the offset of the address of tls_VAR in memory relative to address 0. Or in other words, it would use the absolute address of tls_VAR in memory. What's needed here is the offset relative to the beginning of the TLS data section.
So the compiler must be doing something special here. In order find out, I dumped the relocations in the object file generated while compiling your example C code:
> dumpbin /relocations t215a.obj
...
RELOCATIONS #4
Symbol Symbol
Offset Type Applied To Index Name
-------- ---------------- ----------------- -------- ------
00000008 REL32 00000000 14 _tls_index
00000016 SECREL 00000000 8 tls_VAR
0000002D REL32 00000000 C DoWork
...
As you can see it generates a relocation of type SECREL for the reference to tls_VAR. This makes the relocation relative to the base of the section in the generated executable that that symbol appears in. In this case that's the .tls section, so this relocation generates an offset relative to the beginning of the section used for static TLS data.
So now the question becomes how to get MASM to generate the same SECREL relocation the compiler emits. This turns out to have a easy solution as well, just replace OFFSET FLAT: with SECTIONREL.
So with these changes (and a bit of optimization) your function becomes:
EXTERN tls_VAR:DWORD
EXTERN _tls_index:DWORD
EXTERN DoWork:PROC
PUBLIC norecurse
_TEXT SEGMENT
norecurse PROC
push rbx
sub rsp, 32
mov rax, gs:[88]
mov ecx, _tls_index
mov rbx, [rax + rcx * 8]
cmp DWORD PTR [rbx + SECTIONREL tls_VAR], 0
jne return
mov DWORD PTR [rbx + SECTIONREL tls_VAR], 1
call DoWork
mov DWORD PTR [rbx + SECTIONREL tls_VAR], 0
return:
add rsp, 32
pop rbx
ret
norecurse ENDP
_TEXT ENDS
END
I was able to work a hack around the issue. My implementation in assember is less efficient than the C compiler generated code though because I was not able to figure out how to use the following two addressing modes:
mov rax, QWORD PTR gs:88
mov edi, OFFSET FLAT:tls_VAR
For (1), I had to load 88 into rax and use gs:[rax] to access the TLS-base for the thread.
For (2), the lack of OFFSET FLAT in MASM (ml64.exe) meant that I had to be more clever. I computed the offset by subtracting _tls_start from the TLS-base for the thread that could be applied to TLS-variables in assembler to access their thread local values.
PUBLIC norecurse
EXTRN _tls_index:DWORD
EXTRN _tls_start:DWORD
EXTRN tls_VAR:DWORD
EXTRN DoWork:PROC
_TEXT SEGMENT
norecurse PROC
; non-volatile
push rbx
sub rsp,32
; The gs segment register refers to the base address of the TEB on x64.
; 88 (0×58) is the offset in the TEB for the ThreadLocalStoragePointer member on x64
mov rax,88
mov edx, DWORD PTR _tls_index
mov rax, gs:[rax]
mov r11, QWORD PTR [rax+rdx*8]
lea r10, _tls_start
; r11 will be the the offset-adjusted TLS-Base
sub r11, r10
; ebx will be the the thread local address of tls_VAR
lea rdx, tls_VAR
lea rbx,[r11+rdx]
cmp DWORD PTR [rbx], 0
jne #F
mov DWORD PTR [rbx], 1
call DoWork
mov DWORD PTR [rbx], 0
##:
add rsp,32
pop rbx
ret
norecurse ENDP
_TEXT ENDS
END
I'd love to see more efficient method or pointers on how to actually use the two addressing modes I couldn't figure out with MASM (ml64.exe) though.
Check out TlsGetValue, TlsSetvalue, and friends.

Debugging of a C program (Redis server)

Premise
Hi,
I received multiple reports from a Redis user that experienced server crashes, using a Redis stable release (latest, 2.4.6). The bug is strange since the user is not doing esoteric things, just working a lot with the sorted set type, and only with the ZADD, ZREM, and ZREVRANK commands. However it is strange that a bug like that, causing crashes after a few billion operations executed, was only experienced by a single user. Fortunately the user in question is extremely helpful and collaborated a lot in the tracking of the issue, so I was able to obtain many times logs with the exact sequence of operations performed by Redis, that I re-played locally without result, I also tried to write scripts to closely mimic the kind of work load, to perform in-depth code reviews of the skip list implementation, and so forth.
Even after all this efforts no way to reproduce the issue locally.
It is also worth to mention that at some point the the user started sending the exact same traffic to another box running the same Redis version, but compiled with another gcc, and running in different hardware: so far no issues in this second instance. Still I want to understand what is happening.
So finally I setup a different strategy with the user and asked him to run Redis using gdb, in order to obtain a core file. Finally Redis crashed again and I now have both the core file and the executable. Unfortunately I forgot to ask the user to compile Redis without optimizations.
I need the stack overflow community help since with GDB I reach some conclusion but I've no really idea what could be happening here: at some point a function computes a pointer, and when it calls another function magically that pointer is different, pointing to a memory location that does not hold the right kind of data.
GDB session
The original executable was compiled with GCC 4.4.5-8, this is a GDB session that shows my investigation:
gdb ./redis-server core.16525
GNU gdb (GDB) 7.1-ubuntu
[snip]
Program terminated with signal 11, Segmentation fault.
#0 0x00007f3d9ecd216c in __pthread_rwlock_tryrdlock (rwlock=0x1)
at pthread_rwlock_tryrdlock.c:46
46 pthread_rwlock_tryrdlock.c: No such file or directory.
in pthread_rwlock_tryrdlock.c
Actually the strack trace shown is about a secondary thread doing nothing (you can safely consider Redis a single-threaded app, the other threads are only used to perform things like fsync() against a file descriptor without blocking), let's select the right one.
(gdb) info threads
3 Thread 16525 zslGetRank (zsl=0x7f3d8d71c360, score=19.498544884710096,
o=0x7f3d4cab5760) at t_zset.c:335
2 Thread 16527 0x00007f3d9ecd216c in __pthread_rwlock_tryrdlock (
rwlock=0x6b7f5) at pthread_rwlock_tryrdlock.c:46
* 1 Thread 16526 0x00007f3d9ecd216c in __pthread_rwlock_tryrdlock (rwlock=0x1)
at pthread_rwlock_tryrdlock.c:46
(gdb) thread 3
[Switching to thread 3 (Thread 16525)]#0 zslGetRank (zsl=0x7f3d8d71c360,
score=19.498544884710096, o=0x7f3d4cab5760) at t_zset.c:335
335 t_zset.c: No such file or directory.
in t_zset.c
(gdb) bt
#0 zslGetRank (zsl=0x7f3d8d71c360, score=19.498544884710096, o=0x7f3d4cab5760)
at t_zset.c:335
#1 0x000000000042818b in zrankGenericCommand (c=0x7f3d9dcdc000, reverse=1)
at t_zset.c:2046
#2 0x00000000004108d4 in call (c=0x7f3d9dcdc000) at redis.c:1024
#3 0x0000000000410c1c in processCommand (c=0x7f3d9dcdc000) at redis.c:1130
#4 0x0000000000419d3f in processInputBuffer (c=0x7f3d9dcdc000)
at networking.c:865
#5 0x0000000000419e1c in readQueryFromClient (el=<value optimized out>,
fd=<value optimized out>, privdata=0x7f3d9dcdc000,
mask=<value optimized out>) at networking.c:908
#6 0x000000000040d4a3 in aeProcessEvents (eventLoop=0x7f3d9dc47000,
flags=<value optimized out>) at ae.c:342
#7 0x000000000040d6ee in aeMain (eventLoop=0x7f3d9dc47000) at ae.c:387
#8 0x0000000000412a4f in main (argc=2, argv=<value optimized out>)
at redis.c:1719
We also generated a backtrace. As you can see call() is dispatching the ZREVRANK command, so the zrankGenericCommand() is called with the client structure and the reverse=1 (since it is REV rank) argument. We can easily investigate to check what are the arguments of the ZREVRANK command.
(gdb) up
#1 0x000000000042818b in zrankGenericCommand (c=0x7f3d9dcdc000, reverse=1)
at t_zset.c:2046
2046 in t_zset.c
(gdb) print c->argc
$8 = 3
(gdb) print (redisClient*)c->argc
$9 = (redisClient *) 0x3
(gdb) print (char*)(redisClient*)c->argv[0]->ptr
$10 = 0x7f3d8267ce28 "zrevrank"
(gdb) print (char*)(redisClient*)c->argv[1]->ptr
$11 = 0x7f3d8267ce48 "pc_stat.hkperc"
(gdb) print (long)(redisClient*)c->argv[2]->ptr
$12 = 282472606
So the actual command generating the crash was: ZREVRANK pc_stat.hkperc 282472606
This is consistent with the client logs obtained by the user. Note that I casted the pointer to a long integer for the latest argument, since Redis encodes integers this way to save memory when possible.
Now that's fine, it is now time to investigate the zrankGenericCommand() that called zslGetRan() that caused the actual crash. This is the C source code of zrankGenericCommand around like 2046:
2036 } else if (zobj->encoding == REDIS_ENCODING_SKIPLIST) {
2037 zset *zs = zobj->ptr;
2038 zskiplist *zsl = zs->zsl;
2039 dictEntry *de;
2040 double score;
2041
2042 ele = c->argv[2] = tryObjectEncoding(c->argv[2]);
2043 de = dictFind(zs->dict,ele);
2044 if (de != NULL) {
2045 score = *(double*)dictGetEntryVal(de);
2046 rank = zslGetRank(zsl,score,ele);
2047 redisAssert(rank); /* Existing elements always have a rank. */
2048 if (reverse)
2049 addReplyLongLong(c,llen-rank);
2050 else
2051 addReplyLongLong(c,rank-1);
2052 } else {
2053 addReply(c,shared.nullbulk);
2054 }
2055 }
Ok this is how it works:
We lookup a Redis key, containing a sorted set data type (lookup not included in the code). The Redis Object associated with the key is stored in the zobj local variable.
The zobj ptr field is a pointer to a structure of type zset representing the sorted set.
In turn the zset structure has two pointers, one points to an hash table, and one to a skip list. This is needed since We both provide element-to-score lookups in O(1) for which we need an hash table, but also we take the elements ordered so we use a skip list. In line 2038 the pointer to the skip list (represented by a zskiplist structure) is assigned to the zsl variable.
Later we encode the third argument (line 2042), this is why we casted the value to a long to print it from the client structure.
In line 2043 we lookup the element from the dictionary, and the operation succeeds since we know that the function zslGetRank() side the if branch gets called.
Finally in line 2046 we call zslGetRank() with three arguments: the pointer to the skip list, the score of the element, and the element itself.
Fine... now what is the pointer that zslGetRank() should receive in theory? We can easily investigate this manually looking up the Redis hash table. I hashed manually the key and it maps to bucket 62 of the hash table, let's see if it is true:
(gdb) print (char*)c->db->dict->ht->table[62]->key
$13 = 0x7f3d9dc0f6c8 "pc_stat.hkperc"
Exactly as expected. Let's check the object associated:
(gdb) print *(robj*)c->db->dict->ht->table[62]->val
$16 = {type = 3, storage = 0, encoding = 7, lru = 557869, refcount = 1,
ptr = 0x7f3d9de574b0}
Type = 3, Encoding = 7, it means: it is a sorted set, encoded as a skip list. Fine again.
The sorted set address (ptr field) is 0x7f3d9de574b0, so we can inspect this as well:
(gdb) print *(zset*)0x7f3d9de574b0
$17 = {dict = 0x7f3d9dcf6c20, zsl = 0x7f3d9de591c0}
So we have:
The object associated to the key that points to a sorted set that is stored at address 0x7f3d9de574b0
In turn this sorted set is implemented with a skiplist, at address 0x7f3d9de591c0 (zsl field)
Now let's check if our two variables are set to the right values:
2037 zset *zs = zobj->ptr;
2038 zskiplist *zsl = zs->zsl;
(gdb) info locals
zs = 0x7f3d9de574b0
zsl = 0x7f3d9de591c0
de = <value optimized out>
ele = <value optimized out>
zobj = <value optimized out>
llen = 165312
rank = <value optimized out>
Everything is perfect so far: the variable zs is set to 0x7f3d9de574b0 as expected, and so is the variable zsl pointing to the skiplist, that is set to 0x7f3d9de591c0.
Now this variables are no touched in the course of the code execution:
This are the only lines of code between the assignment of the variables and the call to the zslGetRank() function:
2042 ele = c->argv[2] = tryObjectEncoding(c->argv[2]);
2043 de = dictFind(zs->dict,ele);
2044 if (de != NULL) {
2045 score = *(double*)dictGetEntryVal(de);
2046 rank = zslGetRank(zsl,score,ele);
Nobody is touching zsl, however if we check the stack trace we see that the zslGetRank() function gets called not with the address 0x7f3d9de591c0 as first argument, but with a different one:
#0 zslGetRank (zsl=0x7f3d8d71c360, score=19.498544884710096, o=0x7f3d4cab5760)
at t_zset.c:335
If you read all this you are an hero, and the reward is very small, consisting in this question: do you have an idea, even considering that hardware failure is an option, about how this argument gets modified? It seems very unlikely that the object encoding function or the hash table lookup can corrupt the stack of the caller (but apparently the argument is inside registers already at the time of the call). My assembler is not great, so if you have some clue... it is very welcomed. I'll left you with an info registers output and a disassemble:
(gdb) info registers
rax 0x6 6
rbx 0x7f3d9dcdc000 139902617239552
rcx 0xf742d0b6 4148351158
rdx 0x7f3d95efada0 139902485245344
rsi 0x7f3d4cab5760 139901256030048
rdi 0x7f3d8d71c360 139902342775648
rbp 0x7f3d4cab5760 0x7f3d4cab5760
rsp 0x7fffe61a8040 0x7fffe61a8040
r8 0x7fffe61a7fd9 140737053884377
r9 0x1 1
r10 0x7f3d9dcd4ff0 139902617210864
r11 0x6 6
r12 0x1 1
r13 0x7f3d9de574b0 139902618793136
r14 0x7f3d9de591c0 139902618800576
r15 0x7f3d8267c9e0 139902157572576
rip 0x42818b 0x42818b <zrankGenericCommand+251>
eflags 0x10206 [ PF IF RF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb) disassemble zrankGenericCommand
Dump of assembler code for function zrankGenericCommand:
0x0000000000428090 <+0>: mov %rbx,-0x30(%rsp)
0x0000000000428095 <+5>: mov %r12,-0x20(%rsp)
0x000000000042809a <+10>: mov %esi,%r12d
0x000000000042809d <+13>: mov %r14,-0x10(%rsp)
0x00000000004280a2 <+18>: mov %rbp,-0x28(%rsp)
0x00000000004280a7 <+23>: mov %rdi,%rbx
0x00000000004280aa <+26>: mov %r13,-0x18(%rsp)
0x00000000004280af <+31>: mov %r15,-0x8(%rsp)
0x00000000004280b4 <+36>: sub $0x58,%rsp
0x00000000004280b8 <+40>: mov 0x28(%rdi),%rax
0x00000000004280bc <+44>: mov 0x23138d(%rip),%rdx # 0x659450 <shared+80>
0x00000000004280c3 <+51>: mov 0x8(%rax),%rsi
0x00000000004280c7 <+55>: mov 0x10(%rax),%rbp
0x00000000004280cb <+59>: callq 0x41d370 <lookupKeyReadOrReply>
0x00000000004280d0 <+64>: test %rax,%rax
0x00000000004280d3 <+67>: mov %rax,%r14
0x00000000004280d6 <+70>: je 0x4280ec <zrankGenericCommand+92>
0x00000000004280d8 <+72>: mov $0x3,%edx
0x00000000004280dd <+77>: mov %rax,%rsi
0x00000000004280e0 <+80>: mov %rbx,%rdi
0x00000000004280e3 <+83>: callq 0x41b270 <checkType>
0x00000000004280e8 <+88>: test %eax,%eax
0x00000000004280ea <+90>: je 0x428110 <zrankGenericCommand+128>
0x00000000004280ec <+92>: mov 0x28(%rsp),%rbx
0x00000000004280f1 <+97>: mov 0x30(%rsp),%rbp
0x00000000004280f6 <+102>: mov 0x38(%rsp),%r12
0x00000000004280fb <+107>: mov 0x40(%rsp),%r13
0x0000000000428100 <+112>: mov 0x48(%rsp),%r14
0x0000000000428105 <+117>: mov 0x50(%rsp),%r15
0x000000000042810a <+122>: add $0x58,%rsp
0x000000000042810e <+126>: retq
0x000000000042810f <+127>: nop
0x0000000000428110 <+128>: mov %r14,%rdi
0x0000000000428113 <+131>: callq 0x426250 <zsetLength>
0x0000000000428118 <+136>: testw $0x3c0,0x0(%rbp)
0x000000000042811e <+142>: jne 0x4282b7 <zrankGenericCommand+551>
0x0000000000428124 <+148>: mov %eax,%eax
0x0000000000428126 <+150>: mov %rax,0x8(%rsp)
0x000000000042812b <+155>: movzwl (%r14),%eax
0x000000000042812f <+159>: and $0x3c0,%ax
0x0000000000428133 <+163>: cmp $0x140,%ax
0x0000000000428137 <+167>: je 0x4281c8 <zrankGenericCommand+312>
0x000000000042813d <+173>: cmp $0x1c0,%ax
0x0000000000428141 <+177>: jne 0x428299 <zrankGenericCommand+521>
0x0000000000428147 <+183>: mov 0x28(%rbx),%r15
0x000000000042814b <+187>: mov 0x8(%r14),%r13
0x000000000042814f <+191>: mov 0x10(%r15),%rdi
0x0000000000428153 <+195>: mov 0x8(%r13),%r14
0x0000000000428157 <+199>: callq 0x41bcc0 <tryObjectEncoding>
0x000000000042815c <+204>: mov 0x0(%r13),%rdi
0x0000000000428160 <+208>: mov %rax,0x10(%r15)
0x0000000000428164 <+212>: mov %rax,%rsi
0x0000000000428167 <+215>: mov %rax,%rbp
0x000000000042816a <+218>: callq 0x40ede0 <dictFind>
0x000000000042816f <+223>: test %rax,%rax
0x0000000000428172 <+226>: je 0x428270 <zrankGenericCommand+480>
0x0000000000428178 <+232>: mov 0x8(%rax),%rax
0x000000000042817c <+236>: mov %rbp,%rsi
0x000000000042817f <+239>: mov %r14,%rdi
0x0000000000428182 <+242>: movsd (%rax),%xmm0
0x0000000000428186 <+246>: callq 0x427fd0 <zslGetRank>
=> 0x000000000042818b <+251>: test %rax,%rax
0x000000000042818e <+254>: je 0x4282d5 <zrankGenericCommand+581>
0x0000000000428194 <+260>: test %r12d,%r12d
0x0000000000428197 <+263>: je 0x4281b0 <zrankGenericCommand+288>
0x0000000000428199 <+265>: mov 0x8(%rsp),%rsi
0x000000000042819e <+270>: mov %rbx,%rdi
0x00000000004281a1 <+273>: sub %rax,%rsi
0x00000000004281a4 <+276>: callq 0x41a430 <addReplyLongLong>
0x00000000004281a9 <+281>: jmpq 0x4280ec <zrankGenericCommand+92>
0x00000000004281ae <+286>: xchg %ax,%ax
0x00000000004281b0 <+288>: lea -0x1(%rax),%rsi
0x00000000004281b4 <+292>: mov %rbx,%rdi
0x00000000004281b7 <+295>: callq 0x41a430 <addReplyLongLong>
0x00000000004281bc <+300>: nopl 0x0(%rax)
0x00000000004281c0 <+304>: jmpq 0x4280ec <zrankGenericCommand+92>
0x00000000004281c5 <+309>: nopl (%rax)
0x00000000004281c8 <+312>: mov 0x8(%r14),%r14
0x00000000004281cc <+316>: xor %esi,%esi
0x00000000004281ce <+318>: mov %r14,%rdi
0x00000000004281d1 <+321>: callq 0x417600 <ziplistIndex>
0x00000000004281d6 <+326>: test %rax,%rax
0x00000000004281d9 <+329>: mov %rax,0x18(%rsp)
0x00000000004281de <+334>: je 0x428311 <zrankGenericCommand+641>
0x00000000004281e4 <+340>: mov %rax,%rsi
0x00000000004281e7 <+343>: mov %r14,%rdi
0x00000000004281ea <+346>: callq 0x4175c0 <ziplistNext>
0x00000000004281ef <+351>: test %rax,%rax
0x00000000004281f2 <+354>: mov %rax,0x10(%rsp)
0x00000000004281f7 <+359>: je 0x4282f3 <zrankGenericCommand+611>
0x00000000004281fd <+365>: mov 0x18(%rsp),%rdi
0x0000000000428202 <+370>: mov $0x1,%r13d
0x0000000000428208 <+376>: lea 0x10(%rsp),%r15
0x000000000042820d <+381>: test %rdi,%rdi
0x0000000000428210 <+384>: jne 0x428236 <zrankGenericCommand+422>
0x0000000000428212 <+386>: jmp 0x428270 <zrankGenericCommand+480>
0x0000000000428214 <+388>: nopl 0x0(%rax)
0x0000000000428218 <+392>: lea 0x18(%rsp),%rsi
0x000000000042821d <+397>: mov %r14,%rdi
0x0000000000428220 <+400>: mov %r15,%rdx
0x0000000000428223 <+403>: callq 0x425610 <zzlNext>
0x0000000000428228 <+408>: mov 0x18(%rsp),%rdi
0x000000000042822d <+413>: test %rdi,%rdi
0x0000000000428230 <+416>: je 0x428270 <zrankGenericCommand+480>
0x0000000000428232 <+418>: add $0x1,%r13
0x0000000000428236 <+422>: mov 0x8(%rbp),%rsi
0x000000000042823a <+426>: movslq -0x8(%rsi),%rdx
0x000000000042823e <+430>: callq 0x417a40 <ziplistCompare>
0x0000000000428243 <+435>: test %eax,%eax
0x0000000000428245 <+437>: je 0x428218 <zrankGenericCommand+392>
0x0000000000428247 <+439>: cmpq $0x0,0x18(%rsp)
0x000000000042824d <+445>: je 0x428270 <zrankGenericCommand+480>
0x000000000042824f <+447>: test %r12d,%r12d
0x0000000000428252 <+450>: je 0x428288 <zrankGenericCommand+504>
0x0000000000428254 <+452>: mov 0x8(%rsp),%rsi
0x0000000000428259 <+457>: mov %rbx,%rdi
0x000000000042825c <+460>: sub %r13,%rsi
0x000000000042825f <+463>: callq 0x41a430 <addReplyLongLong>
0x0000000000428264 <+468>: jmpq 0x4280ec <zrankGenericCommand+92>
0x0000000000428269 <+473>: nopl 0x0(%rax)
0x0000000000428270 <+480>: mov 0x2311d9(%rip),%rsi # 0x659450 <shared+80>
0x0000000000428277 <+487>: mov %rbx,%rdi
0x000000000042827a <+490>: callq 0x419f60 <addReply>
0x000000000042827f <+495>: jmpq 0x4280ec <zrankGenericCommand+92>
0x0000000000428284 <+500>: nopl 0x0(%rax)
0x0000000000428288 <+504>: lea -0x1(%r13),%rsi
0x000000000042828c <+508>: mov %rbx,%rdi
0x000000000042828f <+511>: callq 0x41a430 <addReplyLongLong>
0x0000000000428294 <+516>: jmpq 0x4280ec <zrankGenericCommand+92>
0x0000000000428299 <+521>: mov $0x44939f,%edi
0x000000000042829e <+526>: mov $0x808,%edx
0x00000000004282a3 <+531>: mov $0x44a674,%esi
0x00000000004282a8 <+536>: callq 0x432010 <_redisPanic>
0x00000000004282ad <+541>: mov $0x1,%edi
0x00000000004282b2 <+546>: callq 0x40c3a0 <_exit#plt>
0x00000000004282b7 <+551>: mov $0x44a7d0,%edi
0x00000000004282bc <+556>: mov $0x7da,%edx
0x00000000004282c1 <+561>: mov $0x44a674,%esi
0x00000000004282c6 <+566>: callq 0x432090 <_redisAssert>
0x00000000004282cb <+571>: mov $0x1,%edi
0x00000000004282d0 <+576>: callq 0x40c3a0 <_exit#plt>
0x00000000004282d5 <+581>: mov $0x448982,%edi
0x00000000004282da <+586>: mov $0x7ff,%edx
0x00000000004282df <+591>: mov $0x44a674,%esi
0x00000000004282e4 <+596>: callq 0x432090 <_redisAssert>
0x00000000004282e9 <+601>: mov $0x1,%edi
0x00000000004282ee <+606>: callq 0x40c3a0 <_exit#plt>
0x00000000004282f3 <+611>: mov $0x44a6e5,%edi
0x00000000004282f8 <+616>: mov $0x7e2,%edx
0x00000000004282fd <+621>: mov $0x44a674,%esi
0x0000000000428302 <+626>: callq 0x432090 <_redisAssert>
0x0000000000428307 <+631>: mov $0x1,%edi
0x000000000042830c <+636>: callq 0x40c3a0 <_exit#plt>
0x0000000000428311 <+641>: mov $0x44a6bd,%edi
0x0000000000428316 <+646>: mov $0x7e0,%edx
0x000000000042831b <+651>: mov $0x44a674,%esi
0x0000000000428320 <+656>: callq 0x432090 <_redisAssert>
0x0000000000428325 <+661>: mov $0x1,%edi
0x000000000042832a <+666>: callq 0x40c3a0 <_exit#plt>
End of assembler dump.
As requested, this is the tryObjectEncoding function:
/* Try to encode a string object in order to save space */
robj *tryObjectEncoding(robj *o) {
long value;
sds s = o->ptr;
if (o->encoding != REDIS_ENCODING_RAW)
return o; /* Already encoded */
/* It's not safe to encode shared objects: shared objects can be shared
* everywhere in the "object space" of Redis. Encoded objects can only
* appear as "values" (and not, for instance, as keys) */
if (o->refcount > 1) return o;
/* Currently we try to encode only strings */
redisAssert(o->type == REDIS_STRING);
/* Check if we can represent this string as a long integer */
if (!string2l(s,sdslen(s),&value)) return o;
/* Ok, this object can be encoded...
*
* Can I use a shared object? Only if the object is inside a given
* range and if this is the main thread, since when VM is enabled we
* have the constraint that I/O thread should only handle non-shared
* objects, in order to avoid race conditions (we don't have per-object
* locking).
*
* Note that we also avoid using shared integers when maxmemory is used
* because very object needs to have a private LRU field for the LRU
* algorithm to work well. */
if (server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS &&
pthread_equal(pthread_self(),server.mainthread)) {
decrRefCount(o);
incrRefCount(shared.integers[value]);
return shared.integers[value];
} else {
o->encoding = REDIS_ENCODING_INT;
sdsfree(o->ptr);
o->ptr = (void*) value;
return o;
}
}
I think I can answer my own question now...
basically this is what happens. zslGetRank() is called by zrankGenericCommand() with first argument into %rdi register. However later this function will use the %rdi register to set an object (and indeed the %rdi register is set to an object that is valid):
(gdb) print *(robj*)0x7f3d8d71c360
$1 = {type = 0, storage = 0, encoding = 1, lru = 517611, refcount = 2,
ptr = 0x1524db19}
The instruction pointer actually pointed to zslGetRank+64 at the time of the crash, I did something wrong with gdb and modified the register before posting the original question.
Also how to verify that zslGetRank() gets the right address as first argument? Because %r14 gets saved on the stack by zslGetRank() so we can inspect the stack to check if there is a the right location. So we dump near the stack pointer:
0x7fffe61a8000: 0x40337fa0a3376aff 0x00007f3d9dcdc000
0x7fffe61a8010: 0x00007f3d9dcdc000 0x00007f3d4cab5760
0x7fffe61a8020: 0x0000000000000001 0x00007f3d9de574b0
---> 0x7fffe61a8030: 0x00007f3d9de591c0 0x000000000042818b
0x7fffe61a8040: 0x0000000000000000 0x00000000000285c0
0x7fffe61a8050: 0x0000000000000000 0x00007f3d9dcdc000
0x7fffe61a8060: 0x0000000000000000 0x00007f3d9dcdc000
0x7fffe61a8070: 0x0000000000000000 0x0004b6b413e12d9a
0x7fffe61a8080: 0x00000000000003d8 0x0000000000000001
As you can see the right address is here in the stack.
So long story short, the function is called with the right address, it is just that gdb can't dump the right stack trace because the %rdi register gets modified and used for another thing inside the function.
So this can be a memory corruption thing, possibly. What I'll do now is to walk the sorted set by hand simulating the work of zslGetRank() so that I can isolate the node that is broken, and check hopefully in which way it is corrupted.
Thanks for your help.
Edit: here you can find a manually annotated disassembled version of zslGetRank() function -> https://gist.github.com/1641112 (I used it to both learn some more assembler and to make my inspection simpler).
In this situation, the first thing I will do, is to use valgrind. The drawback is that valgrind is about x10 slower than native run, and it may change the behaviour because it seems it serialize the threads. But it save me so many times !
Anyway concerning this crash, it occurs in thread 3, the pthread_rwlock_tryrdlock() receive a bad pointer (rwlock is 0x1). It's perhaps a memory corruption caused by others threads. If it's possible try to put a "watch" on this poi
Hope it helps.
Update: the RAM in this box was broken, we found many problems in user's RAM after this one, and now Redis even implements a --test-memory option... Thanks.
Its a longshot at an intuitive guess, but the only thing I could possibly see causing this error is the assignment to pointers at line 2042:
2042 ele = c->argv[2] = tryObjectEncoding(c->argv[2]);
Hope that helps
Can you add the disassembly for zslGetRank ?
If you look at the other, r14 has the right value and rdi has the wrong value but right before the call there is a "mov r14, rdi" so presumably zslGetRank was called with the correct values.

Resources