summaryrefslogtreecommitdiffstats
path: root/src/runtime/write_err_android.go
blob: 2419fc8663e4cdb501094b99a2aadd06ede6e0cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package runtime

import "unsafe"

var (
	writeHeader = []byte{6 /* ANDROID_LOG_ERROR */, 'G', 'o', 0}
	writePath   = []byte("/dev/log/main\x00")
	writeLogd   = []byte("/dev/socket/logdw\x00")

	// guarded by printlock/printunlock.
	writeFD  uintptr
	writeBuf [1024]byte
	writePos int
)

// Prior to Android-L, logging was done through writes to /dev/log files implemented
// in kernel ring buffers. In Android-L, those /dev/log files are no longer
// accessible and logging is done through a centralized user-mode logger, logd.
//
// https://android.googlesource.com/platform/system/core/+/refs/tags/android-6.0.1_r78/liblog/logd_write.c
type loggerType int32

const (
	unknown loggerType = iota
	legacy
	logd
	// TODO(hakim): logging for emulator?
)

var logger loggerType

func writeErr(b []byte) {
	if logger == unknown {
		// Use logd if /dev/socket/logdw is available.
		if v := uintptr(access(&writeLogd[0], 0x02 /* W_OK */)); v == 0 {
			logger = logd
			initLogd()
		} else {
			logger = legacy
			initLegacy()
		}
	}

	// Write to stderr for command-line programs.
	write(2, unsafe.Pointer(&b[0]), int32(len(b)))

	// Log format: "<header>\x00<message m bytes>\x00"
	//
	// <header>
	//   In legacy mode: "<priority 1 byte><tag n bytes>".
	//   In logd mode: "<android_log_header_t 11 bytes><priority 1 byte><tag n bytes>"
	//
	// The entire log needs to be delivered in a single syscall (the NDK
	// does this with writev). Each log is its own line, so we need to
	// buffer writes until we see a newline.
	var hlen int
	switch logger {
	case logd:
		hlen = writeLogdHeader()
	case legacy:
		hlen = len(writeHeader)
	}

	dst := writeBuf[hlen:]
	for _, v := range b {
		if v == 0 { // android logging won't print a zero byte
			v = '0'
		}
		dst[writePos] = v
		writePos++
		if v == '\n' || writePos == len(dst)-1 {
			dst[writePos] = 0
			write(writeFD, unsafe.Pointer(&writeBuf[0]), int32(hlen+writePos))
			for i := range dst {
				dst[i] = 0
			}
			writePos = 0
		}
	}
}

func initLegacy() {
	// In legacy mode, logs are written to /dev/log/main
	writeFD = uintptr(open(&writePath[0], 0x1 /* O_WRONLY */, 0))
	if writeFD == 0 {
		// It is hard to do anything here. Write to stderr just
		// in case user has root on device and has run
		//	adb shell setprop log.redirect-stdio true
		msg := []byte("runtime: cannot open /dev/log/main\x00")
		write(2, unsafe.Pointer(&msg[0]), int32(len(msg)))
		exit(2)
	}

	// Prepopulate the invariant header part.
	copy(writeBuf[:len(writeHeader)], writeHeader)
}

// used in initLogdWrite but defined here to avoid heap allocation.
var logdAddr sockaddr_un

func initLogd() {
	// In logd mode, logs are sent to the logd via a unix domain socket.
	logdAddr.family = _AF_UNIX
	copy(logdAddr.path[:], writeLogd)

	// We are not using non-blocking I/O because writes taking this path
	// are most likely triggered by panic, we cannot think of the advantage of
	// non-blocking I/O for panic but see disadvantage (dropping panic message),
	// and blocking I/O simplifies the code a lot.
	fd := socket(_AF_UNIX, _SOCK_DGRAM|_O_CLOEXEC, 0)
	if fd < 0 {
		msg := []byte("runtime: cannot create a socket for logging\x00")
		write(2, unsafe.Pointer(&msg[0]), int32(len(msg)))
		exit(2)
	}

	errno := connect(fd, unsafe.Pointer(&logdAddr), int32(unsafe.Sizeof(logdAddr)))
	if errno < 0 {
		msg := []byte("runtime: cannot connect to /dev/socket/logdw\x00")
		write(2, unsafe.Pointer(&msg[0]), int32(len(msg)))
		// TODO(hakim): or should we just close fd and hope for better luck next time?
		exit(2)
	}
	writeFD = uintptr(fd)

	// Prepopulate invariant part of the header.
	// The first 11 bytes will be populated later in writeLogdHeader.
	copy(writeBuf[11:11+len(writeHeader)], writeHeader)
}

// writeLogdHeader populates the header and returns the length of the payload.
func writeLogdHeader() int {
	hdr := writeBuf[:11]

	// The first 11 bytes of the header corresponds to android_log_header_t
	// as defined in system/core/include/private/android_logger.h
	//   hdr[0] log type id (unsigned char), defined in <log/log.h>
	//   hdr[1:2] tid (uint16_t)
	//   hdr[3:11] log_time defined in <log/log_read.h>
	//      hdr[3:7] sec unsigned uint32, little endian.
	//      hdr[7:11] nsec unsigned uint32, little endian.
	hdr[0] = 0 // LOG_ID_MAIN
	sec, nsec := walltime()
	packUint32(hdr[3:7], uint32(sec))
	packUint32(hdr[7:11], uint32(nsec))

	// TODO(hakim):  hdr[1:2] = gettid?

	return 11 + len(writeHeader)
}

func packUint32(b []byte, v uint32) {
	// little-endian.
	b[0] = byte(v)
	b[1] = byte(v >> 8)
	b[2] = byte(v >> 16)
	b[3] = byte(v >> 24)
}