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.

Reply via email to