Can someone explain to me why appending to an array works when you do this:
func (s *Sample) Append(name string) {
d := &Stuff{
name: name,
}
s.data = append(s.data, d)
}
Full code here
But not when you do this:
func (s Sample) Append(name string) {
d := &Stuff{
name: name,
}
s.data = append(s.data, d)
}
Is there any reason at all why you would want to use the second example.
As mentioned in the FAQ
Should I define methods on values or pointers?
func (s *MyStruct) pointerMethod() { } // method on pointer
func (s MyStruct) valueMethod() { } // method on value
First, and most important, does the method need to modify the receiver? If it does, the receiver must be a pointer. (Slices and maps act as references, so their story is a little more subtle, but for instance to change the length of a slice in a method the receiver must still be a pointer.)
In the examples above, if pointerMethod modifies the fields of s, the caller will see those changes, but valueMethod is called with a copy of the caller's argument (that's the definition of passing a value), so changes it makes will be invisible to the caller.
In your case, func (s Sample) Append(name string) modifies a copy.
laher reminds us in the comments that using a value instead of pointer also means getting a copy, and respecting the immutable nature of an object::
You'd want to use the non-pointer valueMethod when (for nstance) you're returning a [value derived from an] 'immutable' private property.
See "Why are receivers pass by value in Go?":
Can be useful if for instance you have a small immutable object. The caller can know for certain that this method doesn't modify it's receiver.
They can't know this if the receiver is a pointer without reading the code first.
Go slices are a tricky beast. Internally, a variable of slice type (like []int) looks like this:
struct {
data *int // pointer to the data area
len int
cap int
}
When you pass a slice to a function, this structure is passed by value, while the underlying data area (i.e. what data points to) is not copied. The builtin append() function modifies the data area (or generates a new one) and returns a new slice with updated len, data, and cap values. If you want to overwrite anything that is not part of the underlying data area, you need to pass a pointer to the slice or return a modified slice.
Go passes arguments by value, not by reference, unless you use a pointer. So inside the function, you're not modifying s in any outer scope if you simply pass by value. However, when you pass a pointer, you're able to modify the "real" variable rather than just the copy that exists inside the function.
While most of the answers here capture exactly what happens, I wanted to dig a bit into how/why that's happening. I started with a couple small snippets of code:
pointer method
package main
import "fmt"
type Bar struct{}
func (b *Bar) Print() {
fmt.Println("debosmit ray")
}
func main() {
b := Bar{}
b.Print()
}
value method
package main
import "fmt"
type Bar struct{}
func (b Bar) Print() {
fmt.Println("debosmit ray")
}
func main() {
b := Bar{}
b.Print()
}
Then, I wanted to look at the assembly for just the files (generated using go tool compile -S filename.go > filename.S, for each of the files. Both the outputs are available here (should be available forever).
Let's take a look at the output of diff pointer.S value.S (pointer -> has pointer method, value -> has value method).
14,15c14,15
< "".(*Bar).Print STEXT size=138 args=0x8 locals=0x58
< 0x0000 00000 (bar.go:7) TEXT "".(*Bar).Print(SB), ABIInternal, $88-8
---
> "".Bar.Print STEXT size=138 args=0x0 locals=0x58
> 0x0000 00000 (bar.go:7) TEXT "".Bar.Print(SB), ABIInternal, $88-0
24c24
< 0x001d 00029 (bar.go:7) FUNCDATA $0, gclocals·2a5305abe05176240e61b8620e19a815(SB)
---
> 0x001d 00029 (bar.go:7) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
26c26
< 0x001d 00029 (bar.go:7) FUNCDATA $3, "".(*Bar).Print.stkobj(SB)
---
> 0x001d 00029 (bar.go:7) FUNCDATA $3, "".Bar.Print.stkobj(SB)
126a127,200
> "".(*Bar).Print STEXT dupok size=187 args=0x8 locals=0x58
> 0x0000 00000 (<autogenerated>:1) TEXT "".(*Bar).Print(SB), DUPOK|WRAPPER|ABIInternal, $88-8
> 0x0000 00000 (<autogenerated>:1) MOVQ (TLS), CX
> 0x0009 00009 (<autogenerated>:1) CMPQ SP, 16(CX)
> 0x000d 00013 (<autogenerated>:1) PCDATA $0, $-2
> 0x000d 00013 (<autogenerated>:1) JLS 154
> 0x0013 00019 (<autogenerated>:1) PCDATA $0, $-1
> 0x0013 00019 (<autogenerated>:1) SUBQ $88, SP
> 0x0017 00023 (<autogenerated>:1) MOVQ BP, 80(SP)
> 0x001c 00028 (<autogenerated>:1) LEAQ 80(SP), BP
> 0x0021 00033 (<autogenerated>:1) MOVQ 32(CX), BX
> 0x0025 00037 (<autogenerated>:1) TESTQ BX, BX
> 0x0028 00040 (<autogenerated>:1) JNE 165
> 0x002a 00042 (<autogenerated>:1) NOP
> 0x002a 00042 (<autogenerated>:1) FUNCDATA $0, gclocals·1a65e721a2ccc325b382662e7ffee780(SB)
> 0x002a 00042 (<autogenerated>:1) FUNCDATA $1, gclocals·2589ca35330fc0fce83503f4569854a0(SB)
> 0x002a 00042 (<autogenerated>:1) FUNCDATA $3, "".(*Bar).Print.stkobj(SB)
> 0x002a 00042 (<autogenerated>:1) CMPQ ""..this+96(SP), $0
> 0x0030 00048 (<autogenerated>:1) JEQ 148
> 0x0032 00050 (<unknown line number>) NOP
> 0x0032 00050 (bar.go:8) XORPS X0, X0
> 0x0035 00053 (bar.go:8) MOVUPS X0, ""..autotmp_13+64(SP)
> 0x003a 00058 (bar.go:8) LEAQ type.string(SB), AX
> 0x0041 00065 (bar.go:8) MOVQ AX, ""..autotmp_13+64(SP)
> 0x0046 00070 (bar.go:8) LEAQ ""..stmp_2(SB), AX
> 0x004d 00077 (bar.go:8) MOVQ AX, ""..autotmp_13+72(SP)
> 0x0052 00082 (<unknown line number>) NOP
> 0x0052 00082 ($GOROOT/src/fmt/print.go:274) MOVQ os.Stdout(SB), AX
> 0x0059 00089 ($GOROOT/src/fmt/print.go:274) LEAQ go.itab.*os.File,io.Writer(SB), CX
> 0x0060 00096 ($GOROOT/src/fmt/print.go:274) MOVQ CX, (SP)
> 0x0064 00100 ($GOROOT/src/fmt/print.go:274) MOVQ AX, 8(SP)
> 0x0069 00105 ($GOROOT/src/fmt/print.go:274) LEAQ ""..autotmp_13+64(SP), AX
> 0x006e 00110 ($GOROOT/src/fmt/print.go:274) MOVQ AX, 16(SP)
> 0x0073 00115 ($GOROOT/src/fmt/print.go:274) MOVQ $1, 24(SP)
> 0x007c 00124 ($GOROOT/src/fmt/print.go:274) MOVQ $1, 32(SP)
> 0x0085 00133 ($GOROOT/src/fmt/print.go:274) PCDATA $1, $1
> 0x0085 00133 ($GOROOT/src/fmt/print.go:274) CALL fmt.Fprintln(SB)
> 0x008a 00138 (bar.go:8) MOVQ 80(SP), BP
> 0x008f 00143 (bar.go:8) ADDQ $88, SP
> 0x0093 00147 (bar.go:8) RET
> 0x0094 00148 (<autogenerated>:1) CALL runtime.panicwrap(SB)
> 0x0099 00153 (<autogenerated>:1) XCHGL AX, AX
> 0x009a 00154 (<autogenerated>:1) NOP
> 0x009a 00154 (<autogenerated>:1) PCDATA $1, $-1
> 0x009a 00154 (<autogenerated>:1) PCDATA $0, $-2
> 0x009a 00154 (<autogenerated>:1) CALL runtime.morestack_noctxt(SB)
> 0x009f 00159 (<autogenerated>:1) PCDATA $0, $-1
> 0x009f 00159 (<autogenerated>:1) NOP
> 0x00a0 00160 (<autogenerated>:1) JMP 0
> 0x00a5 00165 (<autogenerated>:1) LEAQ 96(SP), DI
> 0x00aa 00170 (<autogenerated>:1) CMPQ (BX), DI
> 0x00ad 00173 (<autogenerated>:1) JNE 42
> 0x00b3 00179 (<autogenerated>:1) MOVQ SP, (BX)
> 0x00b6 00182 (<autogenerated>:1) JMP 42
> 0x0000 65 48 8b 0c 25 00 00 00 00 48 3b 61 10 0f 86 87 eH..%....H;a....
> 0x0010 00 00 00 48 83 ec 58 48 89 6c 24 50 48 8d 6c 24 ...H..XH.l$PH.l$
> 0x0020 50 48 8b 59 20 48 85 db 75 7b 48 83 7c 24 60 00 PH.Y H..u{H.|$`.
> 0x0030 74 62 0f 57 c0 0f 11 44 24 40 48 8d 05 00 00 00 tb.W...D$#H.....
> 0x0040 00 48 89 44 24 40 48 8d 05 00 00 00 00 48 89 44 .H.D$#H......H.D
> 0x0050 24 48 48 8b 05 00 00 00 00 48 8d 0d 00 00 00 00 $HH......H......
> 0x0060 48 89 0c 24 48 89 44 24 08 48 8d 44 24 40 48 89 H..$H.D$.H.D$#H.
> 0x0070 44 24 10 48 c7 44 24 18 01 00 00 00 48 c7 44 24 D$.H.D$.....H.D$
> 0x0080 20 01 00 00 00 e8 00 00 00 00 48 8b 6c 24 50 48 .........H.l$PH
> 0x0090 83 c4 58 c3 e8 00 00 00 00 90 e8 00 00 00 00 90 ..X.............
> 0x00a0 e9 5b ff ff ff 48 8d 7c 24 60 48 39 3b 0f 85 77 .[...H.|$`H9;..w
> 0x00b0 ff ff ff 48 89 23 e9 6f ff ff ff ...H.#.o...
> rel 5+4 t=17 TLS+0
> rel 61+4 t=16 type.string+0
> rel 73+4 t=16 ""..stmp_2+0
> rel 85+4 t=16 os.Stdout+0
> rel 92+4 t=16 go.itab.*os.File,io.Writer+0
> rel 134+4 t=8 fmt.Fprintln+0
> rel 149+4 t=8 runtime.panicwrap+0
> rel 155+4 t=8 runtime.morestack_noctxt+0
139,143c213,217
< go.info."".(*Bar).Print$abstract SDWARFINFO dupok size=26
< 0x0000 04 2e 28 2a 42 61 72 29 2e 50 72 69 6e 74 00 01 ..(*Bar).Print..
< 0x0010 01 11 62 00 00 00 00 00 00 00 ..b.......
< rel 0+0 t=24 type.*"".Bar+0
< rel 21+4 t=29 go.info.*"".Bar+0
---
> go.info."".Bar.Print$abstract SDWARFINFO dupok size=23
> 0x0000 04 2e 42 61 72 2e 50 72 69 6e 74 00 01 01 11 62 ..Bar.Print....b
> 0x0010 00 00 00 00 00 00 00 .......
> rel 0+0 t=24 type."".Bar+0
> rel 18+4 t=29 go.info."".Bar+0
297c371,392
< type."".Bar SRODATA size=96
---
> type..namedata.*func(main.Bar)- SRODATA dupok size=18
> 0x0000 00 00 0f 2a 66 75 6e 63 28 6d 61 69 6e 2e 42 61 ...*func(main.Ba
> 0x0010 72 29 r)
> type.*func("".Bar) SRODATA dupok size=56
> 0x0000 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 ................
> 0x0010 7f 95 9a 2f 08 08 08 36 00 00 00 00 00 00 00 00 .../...6........
> 0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
> 0x0030 00 00 00 00 00 00 00 00 ........
> rel 24+8 t=1 runtime.memequal64·f+0
> rel 32+8 t=1 runtime.gcbits.01+0
> rel 40+4 t=5 type..namedata.*func(main.Bar)-+0
> rel 48+8 t=1 type.func("".Bar)+0
> type.func("".Bar) SRODATA dupok size=64
> 0x0000 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 ................
> 0x0010 b4 2e bc 27 02 08 08 33 00 00 00 00 00 00 00 00 ...'...3........
> 0x0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
> 0x0030 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
> rel 32+8 t=1 runtime.gcbits.01+0
> rel 40+4 t=5 type..namedata.*func(main.Bar)-+0
> rel 44+4 t=6 type.*func("".Bar)+0
> rel 56+8 t=1 type."".Bar+0
> type."".Bar SRODATA size=112
303c398,399
< 0x0050 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 ................
---
> 0x0050 00 00 00 00 01 00 01 00 10 00 00 00 00 00 00 00 ................
> 0x0060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
309a406,409
> rel 96+4 t=5 type..namedata.Print.+0
> rel 100+4 t=25 type.func()+0
> rel 104+4 t=25 "".(*Bar).Print+0
> rel 108+4 t=25 "".Bar.Print+0
320a421,423
> ""..stmp_2 SRODATA size=16
> 0x0000 00 00 00 00 00 00 00 00 0c 00 00 00 00 00 00 00 ................
> rel 0+8 t=1 go.string."debosmit ray"+0
325,326c428,429
< gclocals·2a5305abe05176240e61b8620e19a815 SRODATA dupok size=9
< 0x0000 01 00 00 00 01 00 00 00 00 .........
---
> gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8
> 0x0000 01 00 00 00 00 00 00 00 ........
329c432
< "".(*Bar).Print.stkobj SRODATA size=24
---
> "".Bar.Print.stkobj SRODATA size=24
333,334d435
< gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8
< 0x0000 01 00 00 00 00 00 00 00 ........
338a440,447
> gclocals·1a65e721a2ccc325b382662e7ffee780 SRODATA dupok size=10
> 0x0000 02 00 00 00 01 00 00 00 01 00 ..........
> gclocals·2589ca35330fc0fce83503f4569854a0 SRODATA dupok size=10
> 0x0000 02 00 00 00 02 00 00 00 00 00 ..........
> "".(*Bar).Print.stkobj SRODATA dupok size=24
> 0x0000 01 00 00 00 00 00 00 00 f0 ff ff ff ff ff ff ff ................
> 0x0010 00 00 00 00 00 00 00 00 ........
> rel 16+8 t=1 type.[1]interface {}+0
Here, it is evident that for the value method case:
a copy of b was created due to the b.Print() invocation
a copy of the string rel 0+8 t=1 go.string."debosmit ray"+0 was set on the copied struct.
Therefore, this further concretizes that when you use value pointers:
a copy of the object is made when you invoke methods on that object
any mutations to internal state, will only reflect on that copy of the object
Related
The Go code is here:
package main
func add(a, b int) int {
sum := 0
sum = a + b
return sum
}
func main() {
println(add(1, 2))
}
The Go version is
$ go version
go version go1.19.1 darwin/amd64
I use the following command to get assembly:
$ go tool compile -N -l -S main.go
main.add STEXT nosplit size=70 args=0x10 locals=0x18 funcid=0x0 align=0x0
0x0000 00000 (main.go:3) TEXT main.add(SB), NOSPLIT|ABIInternal, $24-16
0x0000 00000 (main.go:3) SUBQ $24, SP
0x0004 00004 (main.go:3) MOVQ BP, 16(SP)
0x0009 00009 (main.go:3) LEAQ 16(SP), BP
0x000e 00014 (main.go:3) FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x000e 00014 (main.go:3) FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x000e 00014 (main.go:3) FUNCDATA $5, main.add.arginfo1(SB)
0x000e 00014 (main.go:3) MOVQ AX, main.a+32(SP)
0x0013 00019 (main.go:3) MOVQ BX, main.b+40(SP)
0x0018 00024 (main.go:3) MOVQ $0, main.~r0(SP)
0x0020 00032 (main.go:4) MOVQ $0, main.sum+8(SP)
0x0029 00041 (main.go:5) MOVQ main.a+32(SP), AX
0x002e 00046 (main.go:5) ADDQ main.b+40(SP), AX
0x0033 00051 (main.go:5) MOVQ AX, main.sum+8(SP)
0x0038 00056 (main.go:6) MOVQ AX, main.~r0(SP)
0x003c 00060 (main.go:6) MOVQ 16(SP), BP
0x0041 00065 (main.go:6) ADDQ $24, SP
0x0045 00069 (main.go:6) RET
0x0000 48 83 ec 18 48 89 6c 24 10 48 8d 6c 24 10 48 89 H...H.l$.H.l$.H.
0x0010 44 24 20 48 89 5c 24 28 48 c7 04 24 00 00 00 00 D$ H.\$(H..$....
0x0020 48 c7 44 24 08 00 00 00 00 48 8b 44 24 20 48 03 H.D$.....H.D$ H.
0x0030 44 24 28 48 89 44 24 08 48 89 04 24 48 8b 6c 24 D$(H.D$.H..$H.l$
0x0040 10 48 83 c4 18 c3 .H....
main.main STEXT size=86 args=0x0 locals=0x20 funcid=0x0 align=0x0
0x0000 00000 (main.go:8) TEXT main.main(SB), ABIInternal, $32-0
0x0000 00000 (main.go:8) CMPQ SP, 16(R14)
0x0004 00004 (main.go:8) PCDATA $0, $-2
0x0004 00004 (main.go:8) JLS 79
0x0006 00006 (main.go:8) PCDATA $0, $-1
0x0006 00006 (main.go:8) SUBQ $32, SP
0x000a 00010 (main.go:8) MOVQ BP, 24(SP)
0x000f 00015 (main.go:8) LEAQ 24(SP), BP
0x0014 00020 (main.go:8) FUNCDATA $0, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x0014 00020 (main.go:8) FUNCDATA $1, gclocals·g2BeySu+wFnoycgXfElmcg==(SB)
0x0014 00020 (main.go:9) MOVL $1, AX
0x0019 00025 (main.go:9) MOVL $2, BX
0x001e 00030 (main.go:9) PCDATA $1, $0
0x001e 00030 (main.go:9) NOP
0x0020 00032 (main.go:9) CALL main.add(SB)
0x0025 00037 (main.go:9) MOVQ AX, main..autotmp_0+16(SP)
0x002a 00042 (main.go:9) CALL runtime.printlock(SB)
0x002f 00047 (main.go:9) MOVQ main..autotmp_0+16(SP), AX
0x0034 00052 (main.go:9) CALL runtime.printint(SB)
0x0039 00057 (main.go:9) CALL runtime.printnl(SB)
0x003e 00062 (main.go:9) NOP
0x0040 00064 (main.go:9) CALL runtime.printunlock(SB)
0x0045 00069 (main.go:10) MOVQ 24(SP), BP
0x004a 00074 (main.go:10) ADDQ $32, SP
0x004e 00078 (main.go:10) RET
0x004f 00079 (main.go:10) NOP
0x004f 00079 (main.go:8) PCDATA $1, $-1
0x004f 00079 (main.go:8) PCDATA $0, $-2
0x004f 00079 (main.go:8) CALL runtime.morestack_noctxt(SB)
0x0054 00084 (main.go:8) PCDATA $0, $-1
0x0054 00084 (main.go:8) JMP 0
0x0000 49 3b 66 10 76 49 48 83 ec 20 48 89 6c 24 18 48 I;f.vIH.. H.l$.H
0x0010 8d 6c 24 18 b8 01 00 00 00 bb 02 00 00 00 66 90 .l$...........f.
0x0020 e8 00 00 00 00 48 89 44 24 10 e8 00 00 00 00 48 .....H.D$......H
0x0030 8b 44 24 10 e8 00 00 00 00 e8 00 00 00 00 66 90 .D$...........f.
0x0040 e8 00 00 00 00 48 8b 6c 24 18 48 83 c4 20 c3 e8 .....H.l$.H.. ..
0x0050 00 00 00 00 eb aa ......
rel 33+4 t=7 main.add+0
rel 43+4 t=7 runtime.printlock+0
rel 53+4 t=7 runtime.printint+0
rel 58+4 t=7 runtime.printnl+0
rel 65+4 t=7 runtime.printunlock+0
rel 80+4 t=7 runtime.morestack_noctxt+0
go.cuinfo.producer.<unlinkable> SDWARFCUINFO dupok size=0
0x0000 2d 4e 20 2d 6c 20 72 65 67 61 62 69 -N -l regabi
go.cuinfo.packagename.main SDWARFCUINFO dupok size=0
0x0000 6d 61 69 6e main
main..inittask SNOPTRDATA size=24
0x0000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x0010 00 00 00 00 00 00 00 00 ........
gclocals·g2BeySu+wFnoycgXfElmcg== SRODATA dupok size=8
0x0000 01 00 00 00 00 00 00 00 ........
main.add.arginfo1 SRODATA static dupok size=5
0x0000 00 08 08 08 ff .....
In my understanding, in the processing of the function main call function add, the stack would be(SP is the top of function add):
0~8: ~ro, the return value of add
8~16: sum, the local variable sum
16~24: BP, use to return caller main
32~40: a, the parameter a of add
40~456: b, the parameter b of add
But what is stored in the 24~32? I can not get it by reading assembly.
Where are jump tables located in x86 elf code?
progname: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: c7 45 fc 02 00 00 00 movl $0x2,-0x4(%rbp)
f: 83 7d fc 09 cmpl $0x9,-0x4(%rbp)
13: 0f 87 ac 00 00 00 ja c5 <main+0xc5>
19: 8b 45 fc mov -0x4(%rbp),%eax
1c: 48 8b 04 c5 00 00 00 mov 0x0(,%rax,8),%rax
23: 00
20: R_X86_64_32S .rodata+0x48
24: ff e0 jmpq *%rax
26: bf 00 00 00 00 mov $0x0,%edi
27: R_X86_64_32 .rodata
2b: b8 00 00 00 00 mov $0x0,%eax
30: e8 00 00 00 00 callq 35 <main+0x35>
31: R_X86_64_PLT32 printf-0x4
35: e9 9b 00 00 00 jmpq d5 <main+0xd5>
3a: bf 00 00 00 00 mov $0x0,%edi
3b: R_X86_64_32 .rodata+0xc
3f: b8 00 00 00 00 mov $0x0,%eax
44: e8 00 00 00 00 callq 49 <main+0x49>
45: R_X86_64_PLT32 printf-0x4
49: e9 87 00 00 00 jmpq d5 <main+0xd5>
4e: bf 00 00 00 00 mov $0x0,%edi
4f: R_X86_64_32 .rodata+0x18
53: b8 00 00 00 00 mov $0x0,%eax
58: e8 00 00 00 00 callq 5d <main+0x5d>
59: R_X86_64_PLT32 printf-0x4
5d: eb 76 jmp d5 <main+0xd5>
5f: bf 00 00 00 00 mov $0x0,%edi
60: R_X86_64_32 .rodata
64: b8 00 00 00 00 mov $0x0,%eax
69: e8 00 00 00 00 callq 6e <main+0x6e>
6a: R_X86_64_PLT32 printf-0x4
6e: eb 65 jmp d5 <main+0xd5>
70: bf 00 00 00 00 mov $0x0,%edi
71: R_X86_64_32 .rodata+0xc
75: b8 00 00 00 00 mov $0x0,%eax
7a: e8 00 00 00 00 callq 7f <main+0x7f>
7b: R_X86_64_PLT32 printf-0x4
7f: eb 54 jmp d5 <main+0xd5>
81: bf 00 00 00 00 mov $0x0,%edi
82: R_X86_64_32 .rodata+0x18
86: b8 00 00 00 00 mov $0x0,%eax
8b: e8 00 00 00 00 callq 90 <main+0x90>
8c: R_X86_64_PLT32 printf-0x4
90: eb 43 jmp d5 <main+0xd5>
92: bf 00 00 00 00 mov $0x0,%edi
93: R_X86_64_32 .rodata
97: b8 00 00 00 00 mov $0x0,%eax
9c: e8 00 00 00 00 callq a1 <main+0xa1>
9d: R_X86_64_PLT32 printf-0x4
a1: eb 32 jmp d5 <main+0xd5>
a3: bf 00 00 00 00 mov $0x0,%edi
a4: R_X86_64_32 .rodata+0xc
a8: b8 00 00 00 00 mov $0x0,%eax
ad: e8 00 00 00 00 callq b2 <main+0xb2>
ae: R_X86_64_PLT32 printf-0x4
b2: eb 21 jmp d5 <main+0xd5>
b4: bf 00 00 00 00 mov $0x0,%edi
b5: R_X86_64_32 .rodata+0x18
b9: b8 00 00 00 00 mov $0x0,%eax
be: e8 00 00 00 00 callq c3 <main+0xc3>
bf: R_X86_64_PLT32 printf-0x4
c3: eb 10 jmp d5 <main+0xd5>
c5: bf 00 00 00 00 mov $0x0,%edi
c6: R_X86_64_32 .rodata+0x24
ca: b8 00 00 00 00 mov $0x0,%eax
cf: e8 00 00 00 00 callq d4 <main+0xd4>
d0: R_X86_64_PLT32 printf-0x4
d4: 90 nop
d5: b8 00 00 00 00 mov $0x0,%eax
da: c9 leaveq
db: c3 retq
I have a basic switch case with 10 cases (including default case). If x=2, then it goes to 2nd block in switch case code.
Need help in understanding where exactly gcc generated jump tables are located in elf files and in which section. I pasted output of objdump -dr progname above.
I've been trying to create an ELF executable using libelf, but haven't gotten it running yet. Whenever I try to run it, I just get a segfault. strace outputs:
execve("./test.elf", ["./test.elf"], 0x7ffc28d60660 /* 63 vars */) = -1 EINVAL (Invalid argument)
+++ killed by SIGSEGV +++
readelf gives:
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x1000
Start of program headers: 64 (bytes into file)
Start of section headers: 152 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 1
Size of section headers: 64 (bytes)
Number of section headers: 3
Section header string table index: 2
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000001000 00000078
0000000000000008 0000000000000000 AX 0 0 1
[ 2] .shstrtab STRTAB 0000000000000000 00000080
0000000000000011 0000000000000000 A 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
There are no section groups in this file.
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000078 0x0000000000001000 0x0000000000001000
0x0000000000000008 0x0000000000000008 R E 0x1
Section to Segment mapping:
Segment Sections...
00 .text
There is no dynamic section in this file.
There are no relocations in this file.
The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.
No version information found in this file.
And here's the hexdump of the file:
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 02 00 3e 00 01 00 00 00 00 10 00 00 00 00 00 00 |..>.............|
00000020 40 00 00 00 00 00 00 00 98 00 00 00 00 00 00 00 |#...............|
00000030 00 00 00 00 40 00 38 00 01 00 40 00 03 00 02 00 |....#.8...#.....|
00000040 01 00 00 00 05 00 00 00 78 00 00 00 00 00 00 00 |........x.......|
00000050 00 10 00 00 00 00 00 00 00 10 00 00 00 00 00 00 |................|
00000060 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 |................|
00000070 01 00 00 00 00 00 00 00 31 c0 ff c0 b3 2a cd 80 |........1....*..|
00000080 00 2e 74 65 78 74 00 2e 73 68 73 74 72 74 61 62 |..text..shstrtab|
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000000d0 00 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 |................|
000000e0 06 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 |................|
000000f0 78 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 |x...............|
00000100 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
00000110 00 00 00 00 00 00 00 00 07 00 00 00 03 00 00 00 |................|
00000120 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000130 80 00 00 00 00 00 00 00 11 00 00 00 00 00 00 00 |................|
00000140 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
00000150 00 00 00 00 00 00 00 00 |........|
00000158
From what I've read and comparing it to working executables, this should work. But it doesn't, and I have no idea why. Any help is appreciated, thanks.
I figured it out! When I was looking at a working executable for reference, I was looking at a PIE, but I was trying to create a non-PIE. I didn't realize the EHDR type needs to be ET_DYN for a PIE. Also, a few of the addresses were wrong.
The new readelf:
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x1078
Start of program headers: 64 (bytes into file)
Start of section headers: 152 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 1
Size of section headers: 64 (bytes)
Number of section headers: 3
Section header string table index: 2
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS 0000000000001078 00000078
0000000000000008 0000000000000000 AX 0 0 1
[ 2] .shstrtab STRTAB 0000000000000000 00000080
0000000000000011 0000000000000000 A 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
There are no section groups in this file.
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000000078 0x0000000000001078 0x0000000000001078
0x0000000000000008 0x0000000000000008 R E 0x1
Section to Segment mapping:
Segment Sections...
00 .text
There is no dynamic section in this file.
There are no relocations in this file.
The decoding of unwind sections for machine type Advanced Micro Devices X86-64 is not currently supported.
No version information found in this file.
And the new hexdump:
00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |.ELF............|
00000010 03 00 3e 00 01 00 00 00 78 10 00 00 00 00 00 00 |..>.....x.......|
00000020 40 00 00 00 00 00 00 00 98 00 00 00 00 00 00 00 |#...............|
00000030 00 00 00 00 40 00 38 00 01 00 40 00 03 00 02 00 |....#.8...#.....|
00000040 01 00 00 00 05 00 00 00 78 00 00 00 00 00 00 00 |........x.......|
00000050 78 10 00 00 00 00 00 00 78 10 00 00 00 00 00 00 |x.......x.......|
00000060 08 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 |................|
00000070 01 00 00 00 00 00 00 00 31 c0 ff c0 b3 2a cd 80 |........1....*..|
00000080 00 2e 74 65 78 74 00 2e 73 68 73 74 72 74 61 62 |..text..shstrtab|
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
000000d0 00 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 |................|
000000e0 06 00 00 00 00 00 00 00 78 10 00 00 00 00 00 00 |........x.......|
000000f0 78 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 |x...............|
00000100 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
00000110 00 00 00 00 00 00 00 00 07 00 00 00 03 00 00 00 |................|
00000120 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000130 80 00 00 00 00 00 00 00 11 00 00 00 00 00 00 00 |................|
00000140 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 |................|
00000150 00 00 00 00 00 00 00 00 |........|
00000158
I am in the process of learning Assembly, and I tried writing my own bootloader. It works fine on VirtualBox , but it doesn't work on a actual PC.
On pc 'Hello World!' doesn't get printed.
This is the code is use:
BITS 16
ORG 0x7C00
jmp boot_sector
;------------------------------
OEMLabel db "FLOPPYDR"
BytesPerSector dw 512
SectorsPerCluster db 1
ReservedForBoot dw 1
NumberOfFats db 2
RootDirEntries dw 224
LogicalSectors dw 2880
MediumByte db 0xF0
SectorsPerFat dw 9
SectorsPerTrack dw 18
Sides dw 2
HiddenSectors dd 0
LargeSectors dd 0
DriveNo dw 0
Signature db 41
VolumeID dd 0x00
VolumeLabel db "FLOPPYDRIVE"
FileSystem db "FAT12"
;##############################
boot_sector:
;##############################
mov ax, 0x0000 ; Set up the stack
mov ss, ax ; Is this done correctly?
mov sp, 0x7C00 ; (I dont quite understand)
int 0x10 ; Set video mode
int 0x13 ; Reset the drive
mov ah, 0x02 ; Read more sectors
mov al, 2 ; Read two extra sectors,
mov bx, main_sector ; starting from the second.
mov ch, 0 ;
mov cl, 2 ; dl has been set already (?)
mov dh, 0 ;
int 0x13 ;
mov [bootdev], dl ; Store original dl in bootdev
jmp main_sector ; Go to the main sector (0x200 I think)
times 510 - ($ - $$) db 0 ; Fill in the rest of the sector with 0s
dw 0xAA55 ; and 0xAA55 at the end for signature
;##############################
main_sector:
;##############################
jmp Start
;------------------------------
bootdev db 0
msg db 'Hello World!', 10, 13, 0
;------------------------------
print_string:
mov ah, 0x0E
mov bh, 0
cmp al, 0
jne .loop
mov bl, 0x0F
.loop:
lodsb
cmp al, 0
je .end
int 0x10
jmp .loop
.end:
ret
;------------------------------
Start:
mov si, msg
call print_string
hlt
times 512 - ($ - main_sector) db 0
I've also commented in some questions, but these are not my main question (well, maybe me not knowing the answer causes the problem). Why doesn't this work on a real PC?
To compile I use nasm -f bin boot.asm -o boot.bin and to create a virtual floppydisk file I use mkfile 1474560 floppy.flp
Then I open up floppy.flp using HexEdit and replace the first 64 lines (0x00 - 0x3F) with the content of the boot.bin file (opened using HexEdit).
E9 38 00 46 4C 4F 50 50 59 44 52 00 02 01 01 00
02 E0 00 40 0B F0 09 00 12 00 02 00 00 00 00 00
00 00 00 00 00 00 29 00 00 00 00 46 4C 4F 50 50
59 44 52 49 56 45 46 41 54 31 32 B8 00 00 8E D0
BC 00 7C CD 10 CD 13 B4 02 B0 02 BB 00 7E B5 00
B1 02 B6 00 CD 13 88 16 03 7E E9 A3 01 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA <-- End of first sector
E9 24 00 00 48 65 6C 6C 6F 20 57 6F 72 6C 64 21
0A 0D 00 B4 0E B7 00 3C 00 75 02 B3 0F AC 3C 00
74 04 CD 10 EB F7 C3 BE 04 7E E8 E6 FF F4 00 00 (The rest is just 0's).
This is the Terminal when I burn floppy.flp to the USB drive:
Last login: Wed Sep 23 12:10:48 on ttys000
MacBook-Air:~ sasha$ cd ~/Desktop
MacBook-Air:Desktop sasha$ diskutil list
/dev/disk0
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *121.3 GB disk0
1: Apple_HFS 209.7 MB disk0s1
2: Apple_CoreStorage 120.5 GB disk0s2
3: Apple_Boot Recovery HD 650.0 MB disk0s3
/dev/disk1
#: TYPE NAME SIZE IDENTIFIER
0: Apple_HFS Macintosh HD *120.1 GB disk1
Logical Volume on disk0s2
8CD6A846-395D-4C97-A5DE-0A7ABA9F1C99
Unencrypted
/dev/disk2
#: TYPE NAME SIZE IDENTIFIER
0: Apple_partition_scheme *17.1 MB disk2
1: Apple_partition_map 32.3 KB disk2s1
2: Apple_HFS Flash Player 17.1 MB disk2s2
/dev/disk3
#: TYPE NAME SIZE IDENTIFIER
0: FLOPPYDRIVE *1.0 GB disk3
MacBook-Air:Desktop sasha$ diskutil unmountdisk /dev/disk3
Unmount of all volumes on disk3 was successful
MacBook-Air:Desktop sasha$ sudo dd bs=512 if=floppy.flp of=/dev/disk3
Password:
2880+0 records in
2880+0 records out
1474560 bytes transferred in 0.843982 secs (1747146 bytes/sec)
MacBook-Air:Desktop sasha$
I've written about bootloaders at some length recently on Stackoverflow. Most issues that involve the situation where it works on one emulator or VM but not on another (or physical hardware) usually come down to making false assumptions about the state of the segment registers when the BIOS jumps to your code. Under some emulators the segment registers may have more sane values in them, but that usually isn't the case. From my previous answer I had these two tips that seem to apply here:
When the BIOS jumps to your code you can't rely on DS,ES,SS,SP registers having valid or expected values. They should be set up appropriately when your bootloader starts.
The direction flag used by lodsb, movsb etc could be either set or cleared. If the direction flag is set improperly SI/DI registers may be adjusted in the wrong direction. Use STD/CLD to set it to the direction you wish (CLD=forward/STD=backwards). In this case the code assumes forward movement so one should use CLD. More on this can be found in an instruction set reference
Your assembly code is set to compile and link assuming an origin point of 0x7C00 (via ORG 0x7C00). Your code that accesses variables like msg and bootdev will be made with the assumption that their memory address will be absolute within the segment (DS). This means that if you have an invalid DS segment then you could be addressing variables, data, and labels at the wrong locations. As an example:
mov [bootdev], dl
has an implicit reference to DS and is equivalent to addressing it with an explicit DS segment:
mov [ds:bootdev], dl
If DS has some random value in it then you'll likely be accessing memory in places you don't expect. For some environments DS might just be zero so your code will work.
How do you know which segment to use? The bootloader is loaded by the BIOS at physical memory 0x0000:0x7C00(segment:offset) . Your origin point (Set with ORG directive) matches the offset so that means in your case DS should be set to zero.
In your code ES should also be set to zero. The reason is that INT 0x13 AH=0x02 (disk read) says:
ES:BX Buffer Address Pointer
Imagine if ES is set to random garbage, the disk read will likely read into memory you didn't intend. So just like DS, ES must also be set. You've written your bootloader and kernel in the same file with the origin point of 0x7C00 so again you just need to use an ES segment set to zero.
When setting up your stack you can set ES and DS appropriately.
mov ax, 0x0000 ; Set up the stack
mov ss, ax ; Is this done correctly?
mov sp, 0x7C00 ; (I dont quite understand)
mov ds, ax ; Set DS to 0 because that is what your code needs
mov es, ax ; ES the same as DS.
cld ; Read my tip #2
You did ask if you set your stack properly. There is nothing wrong with it. Your instructions effectively set up a a stack that grows downward from 0x0000:0x7C00 just below the area occupied by your bootloader. That leaves about 27kb (0x7C00-0x1000) of stack space. 4k is plenty for BIOS calls and your current code. The first 0x1000 of memory are generally for interrupt table/BIOS data area etc.
One other bug I noticed in your code was when you try to reset the disk drive:
int 0x10 ; Set video mode
int 0x13 ; Reset the drive
You set AX to zero above these 2 lines. INT 0x10 AH=0x00 (set video mode) has the side effect of returning information in AX. Since AX can be clobbered your call to INT 0x13 AH=0x00 will likely be wrong. You need to clear AH (or all of AX) prior to calling int 0x13 to reset the drive. The code should look like:
int 0x10 ; Set video mode
xor ax,ax ; clear AX (AH=0)
int 0x13 ; Reset the drive
There is a minor issue at the top of your program and would likely only be a problem if you put this bootloader on a properly formatted FAT12 disk image and tried to mount it in your OS. You have:
jmp boot_sector
;------------------------------
OEMLabel db "FLOPPYDR"
The disk structure you have within your bootloader should have OEMLabel starting from the 4th byte. jmp boot_sector could be encoded as a 2 or 3 byte instruction by NASM. Use short to force a 2 byte encoding followed by a NOP (1 byte instruction). This will place OEMLabel at the 4th byte in the file. It could look like this:
jmp short boot_sector
nop
;------------------------------
OEMLabel db "FLOPPYDR"
Alternatively you can encode a JMP that may be 2 or 3 bytes when encoded and pad it with NOP if necessary using NASM's TIMES directive so that OEMLabel always starts at the 4th byte:
jmp boot_sector
times 3-($-$$) nop
;------------------------------
OEMLabel db "FLOPPYDR"
A trick to avoid using hexedit to manually insert the bootloader code at the beginning of a disk image is to use dd. You can use dd to overwrite the first 1024 bytes and keep the rest intact. Try dd if=boot.bin of=floppy.flp bs=512 count=2 conv=notrunc . This should open up floppy.flp write 2 512 byte sectors containing the 1024 bytes from boot.bin without truncating the file (conv=notrunc)
When a bootloader is started the contents of almost all registers (including segment registers) is "undefined". The only register that actually does have a known value is DL (which contains the BIOS' drive number for the disk).
All instructions that refer to memory use an implied or explicit segment register. For example, both mov [bootdev], dl and lodsb rely on the (implied) DS segment register, which is never set and is still undefined.
Like all undefined values, it's possible (due to pure luck) for them to be a value that makes things work by accident. Your code would work if the BIOS happened to leave the value 0x0000 in DS.
I'm trying to compile my code into raw binary and as suggested by other SO posts (like this and this) I tried objdump:
$ gcc -c foo.c
$ objcopy -O binary foo.o foo.bin
Then I tried to make sure if this is valid:
$ objdump -d foo.o
foo.o: file format elf64-x86-64
Disassembly of section .text:
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: 48 83 ec 10 sub $0x10,%rsp
8: 89 7d fc mov %edi,-0x4(%rbp)
b: 48 89 75 f0 mov %rsi,-0x10(%rbp)
f: bf 00 00 00 00 mov $0x0,%edi
14: b8 00 00 00 00 mov $0x0,%eax
19: e8 00 00 00 00 callq 1e <main+0x1e>
1e: b8 00 00 00 00 mov $0x0,%eax
23: c9 leaveq
24: c3 ret
$ hexdump -C foo.bin
00000000 14 00 00 00 00 00 00 00 01 7a 52 00 01 78 10 01 |.........zR..x..|
00000010 1b 0c 07 08 90 01 00 00 1c 00 00 00 1c 00 00 00 |................|
00000020 00 00 00 00 25 00 00 00 00 41 0e 10 86 02 43 0d |....%....A....C.|
00000030 06 60 0c 07 08 00 00 00 |.`......|
00000038
Evidently something is wrong. I checked this with the results of a gcc cross-compilation, with much the same obviously incorrect results.
You can pass -j .text to objcopy.