goplus / llgo

A Go compiler based on LLVM in order to better integrate Go with the C ecosystem including Python
Apache License 2.0
365 stars 26 forks source link

os:fix os.Errno 's nil pointer derefer in linux #828

Closed luoliwoshang closed 1 month ago

luoliwoshang commented 1 month ago

fix #825

The panic was in the use os.Errno. in the generate ir in llgo like this demo.

; ModuleID = 'errno'
source_filename = "errno"

@errno = external global i32, align 4

declare i32 @putchar(i32)

define i32 @main() {
entry:
  %0 = load i32, ptr @errno, align 4
  %1 = add i32 %0, 48  ; Convert to ASCII digit (assuming errno is 0-9)
  call i32 @putchar(i32 %1)
  call i32 @putchar(i32 10)  ; Print newline
  ret i32 0
}

will cause follow error

/usr/bin/ld: errno: TLS definition in /lib/aarch64-linux-gnu/libc.so.6 section .tbss mismatches non-TLS reference in demo.o
/usr/bin/ld: /lib/aarch64-linux-gnu/libc.so.6: error adding symbols: bad value
clang: error: linker command failed with exit code 1 (use -v to see invocation)

we can see the marcos define the errno is actually call the__errno_location. in linux

extern int *__errno_location (void) __THROW __attribute_const__;
# define errno (*__errno_location ())

in macos is call __error

extern int * __error(void);
#define errno (*__error())

so we need another way to get the errno

; ModuleID = 'errno'
source_filename = "errno"

declare i32* @__errno_location()
declare i32 @putchar(i32)

define i32 @main() {
entry:
  %errno_ptr = call i32* @__errno_location()
  %errno_val = load i32, i32* %errno_ptr, align 4
  %ascii_val = add i32 %errno_val, 48  
  call i32 @putchar(i32 %ascii_val)
  call i32 @putchar(i32 10)  
  ret i32 0
}
root@be00d9b1c2c9:~/llgo/chore/_xtool/llcppsymg/oserrno/llvm# llvm-as demo.ll -o demo.bc
root@be00d9b1c2c9:~/llgo/chore/_xtool/llcppsymg/oserrno/llvm# llc -filetype=obj demo.bc -o demo.o
root@be00d9b1c2c9:~/llgo/chore/_xtool/llcppsymg/oserrno/llvm# clang demo.o -o demo
root@be00d9b1c2c9:~/llgo/chore/_xtool/llcppsymg/oserrno/llvm# ./demo
0

so,by wrapping the errno macro in a C function, we can obtain the actual errno value through a function call across different platforms.

 #include <errno.h>
 int llgoErrno() { return errno; }
//go:linkname Errno C.llgoErrno
func Errno() c.Int

result

we can get expected result of os.Errno's use

In this demo, when attempting to open a non-existent file, the program uses os.Error to retrieve the actual reason for the operation failure. This tests whether os.Error() is correctly referenced and functioning. The output showing "No such file or directory" confirms that the errno handling is working as expected across different platforms.

macos

❯ llgo run .
noexist: stat /var/folders/5j/sgtxqmbn1hbdqtgx5kkp6y700000gn/T/noexist.txt: No such file or directory
noexist: open /var/folders/5j/sgtxqmbn1hbdqtgx5kkp6y700000gn/T/noexist.txt: No such file or directory

linux

noexist: stat /tmp/noexist.txt: No such file or directory
noexist: open /tmp/noexist.txt: No such file or directory
codecov[bot] commented 1 month ago

Codecov Report

All modified and coverable lines are covered by tests :white_check_mark:

Project coverage is 97.47%. Comparing base (9ea88fe) to head (e9177c8). Report is 8 commits behind head on main.

Additional details and impacted files ```diff @@ Coverage Diff @@ ## main #828 +/- ## ======================================= Coverage 97.47% 97.47% ======================================= Files 20 20 Lines 5157 5157 ======================================= Hits 5027 5027 Misses 110 110 Partials 20 20 ```

:umbrella: View full report in Codecov by Sentry.
:loudspeaker: Have feedback on the report? Share it here.

xushiwei commented 1 month ago

please review @visualfc. If os.Errno has bugs, it means os.Stdout/Stderr also has bugs.

luoliwoshang commented 1 month ago

please review @visualfc. If os.Errno has bugs, it means os.Stdout/Stderr also has bugs.

os.Stdout/Stderr/Stdin works normally both in macos & linux

package main

import (
    "io"
    "fmt"
    "os"
)

func main() {
    fmt.Println("Message 1")
    fmt.Fprintln(os.Stdout, "Message 2")
    fmt.Fprintln(os.Stderr, "Message 3")

    input, err := io.ReadAll(os.Stdin)
    if err != nil {
        fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err)
        return
    }
    fmt.Fprintf(os.Stdout, "You entered:\n%s", input)
}
root@be00d9b1c2c9:~/llgo/_demo/stdout# echo "Hello, World!" | llgo run demo.go
Message 1
Message 2
Message 3
You entered:
Hello, World!
root@be00d9b1c2c9:~/llgo/_demo/stdout# echo "Hello, World!" | go run demo.go
Message 1
Message 2
Message 3
You entered:
Hello, World!
luoliwoshang commented 1 month ago

please review @visualfc. If os.Errno has bugs, it means os.Stdout/Stderr also has bugs.

/usr/include/stdio.h

/* Standard streams.  */
extern FILE *stdin;     /* Standard input stream.  */
extern FILE *stdout;        /* Standard output stream.  */
extern FILE *stderr;        /* Standard error output stream.  */
/* C89/C99 say they're macros.  Make them happy.  */
#define stdin stdin
#define stdout stdout
#define stderr stderr