Filed issue https://github.com/golang/go/issues/22703
On Monday, November 13, 2017 at 3:50:58 PM UTC-8, Keith Randall wrote: > > Konstantin, your description is correct. The code is trying to load a > function pointer out of an itab, but the pointer to the itab is nil. > I think this is actually a bug. If you have an interface with more than > pagesize/ptrsize methods in it, this code might not panic when it should. > > > On Monday, November 13, 2017 at 4:46:59 AM UTC-8, Alexander Kapshuk wrote: >> >> On Mon, Nov 13, 2017 at 1:42 PM, Konstantin Khomoutov <kos...@bswap.ru> >> wrote: >> >>> While debugging a program which had a panic due to an attempt to call a >>> method on a value of an interface typeš, I came across the behaviour I >>> find strange, and would like to get help understanding what happens. >>> >>> The behaviour is exhibited by this simple program: >>> >>> -------------------------------8<-------------------------------- >>> 1 package main >>> 2 >>> 3 import ( >>> 4 "fmt" >>> 5 "os" >>> 6 ) >>> 7 >>> 8 func main() { >>> 9 var fi os.FileInfo >>> 10 s := fi.Name() >>> 11 fmt.Println(s) >>> 12 } >>> -------------------------------8<-------------------------------- >>> >>> When built by Go 1.8.3 on Linux/amd64 and run on that same system >>> it expectedly panics at line 10. >>> >>> >>> What puzzles me, is that the address it panics is not 0x0 (which I would >>> expect from an x86/amd64 H/W platform to stand for nil) but 0x38: >>> >>> -------------------------------8<-------------------------------- >>> $ go run foo.go >>> panic: runtime error: invalid memory address or nil pointer dereference >>> [signal SIGSEGV: segmentation violation code=0x1 addr=0x38 pc=0x47d148] >>> >>> goroutine 1 [running]: >>> main.main() >>> /home/user/foo.go:10 +0x28 >>> exit status 2 >>> -------------------------------8<-------------------------------- >>> >>> >>> If I run `go tool objdump` on the generated binary, I get this >>> (instruction codes removed for brewity): >>> >>> -------------------------------8<-------------------------------- >>> TEXT main.main(SB) /home/user/foo.go >>> foo.go:8 0x47d120 FS MOVQ FS:0xfffffff8, CX >>> foo.go:8 0x47d129 CMPQ 0x10(CX), SP >>> foo.go:8 0x47d12d JBE 0x47d1d3 >>> foo.go:8 0x47d133 SUBQ $0x58, SP >>> foo.go:8 0x47d137 MOVQ BP, 0x50(SP) >>> foo.go:8 0x47d13c LEAQ 0x50(SP), BP >>> foo.go:10 0x47d141 MOVQ $0x38, AX >>> foo.go:10 0x47d148 MOVQ 0(AX), AX >>> foo.go:10 0x47d14b MOVQ $0x0, 0(SP) >>> foo.go:10 0x47d153 CALL AX >>> foo.go:10 0x47d155 MOVQ 0x10(SP), AX >>> foo.go:10 0x47d15a MOVQ 0x8(SP), CX >>> foo.go:11 0x47d15f MOVQ CX, 0x30(SP) >>> foo.go:11 0x47d164 MOVQ AX, 0x38(SP) >>> foo.go:11 0x47d169 MOVQ $0x0, 0x40(SP) >>> foo.go:11 0x47d172 MOVQ $0x0, 0x48(SP) >>> foo.go:11 0x47d17b LEAQ 0xf3de(IP), AX >>> foo.go:11 0x47d182 MOVQ AX, 0(SP) >>> foo.go:11 0x47d186 LEAQ 0x30(SP), AX >>> foo.go:11 0x47d18b MOVQ AX, 0x8(SP) >>> foo.go:11 0x47d190 CALL runtime.convT2E(SB) >>> foo.go:11 0x47d195 MOVQ 0x10(SP), AX >>> foo.go:11 0x47d19a MOVQ 0x18(SP), CX >>> foo.go:11 0x47d19f MOVQ AX, 0x40(SP) >>> foo.go:11 0x47d1a4 MOVQ CX, 0x48(SP) >>> foo.go:11 0x47d1a9 LEAQ 0x40(SP), AX >>> foo.go:11 0x47d1ae MOVQ AX, 0(SP) >>> foo.go:11 0x47d1b2 MOVQ $0x1, 0x8(SP) >>> foo.go:11 0x47d1bb MOVQ $0x1, 0x10(SP) >>> foo.go:11 0x47d1c4 CALL fmt.Println(SB) >>> foo.go:12 0x47d1c9 MOVQ 0x50(SP), BP >>> foo.go:12 0x47d1ce ADDQ $0x58, SP >>> foo.go:12 0x47d1d2 RET >>> foo.go:8 0x47d1d3 CALL runtime.morestack_noctxt(SB) >>> foo.go:8 0x47d1d8 JMP main.main(SB) >>> -------------------------------8<-------------------------------- >>> >>> So, for the call at line 10 we have >>> >>> MOVQ $0x38, AX >>> MOVQ 0(AX), AX >>> >>> which I translate as "load the quad word 0x38 into the register AX >>> and then load the quad word located at offset 0 in the memory at >>> the address located in the register AX, into that same register". >>> >>> That second instruction fails (since IIRC Linux maps a special >>> sentinel page at address 0x0 to catch problems like this one). >>> >>> >>> I fail to comprehend why 0x38 appears to be a constant (some magic >>> number). Looks like this is an offset of something. Recalling [1], >>> I found out Go 1.8.3 defines an Itab as >>> >>> type itab struct { >>> inter *interfacetype >>> _type *_type >>> link *itab >>> bad int32 >>> inhash int32 // has this itab been added to hash? >>> fun [1]uintptr // variable sized >>> } >>> >>> 0x38 is 56, and 56/sizeof(quad word) = 7, so the only further guess >>> I can make is that 0x38 is the offset of the 3rd element of the "fun" >>> field in an Itab. >>> >>> Am I correct? >>> If not, what does that 0x38 stand for? >>> >>> 1. https://research.swtch.com/interfaces >>> >>> -- >>> You received this message because you are subscribed to the Google >>> Groups "golang-nuts" group. >>> To unsubscribe from this group and stop receiving emails from it, send >>> an email to golang-nuts...@googlegroups.com. >>> For more options, visit https://groups.google.com/d/optout. >>> >> >> Incidentally, this code in question does panic at addr=0x0 when run from >> within the Go Playground. >> >> -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.