memgrind.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  1. // Copyright 2021 The Libc Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. //go:build !libc.membrk && libc.memgrind && !(linux && (amd64 || arm64 || loong64 || ppc64le || s390x || riscv64 || 386 || arm))
  5. // This is a debug-only version of the memory handling functions. When a
  6. // program is built with -tags=libc.memgrind the functions MemAuditStart and
  7. // MemAuditReport can be used to check for memory leaks.
  8. package libc // import "modernc.org/libc"
  9. import (
  10. "fmt"
  11. "runtime"
  12. "sort"
  13. "strings"
  14. "unsafe"
  15. "modernc.org/libc/errno"
  16. "modernc.org/libc/sys/types"
  17. "modernc.org/memory"
  18. )
  19. const memgrind = true
  20. type memReportItem struct {
  21. p, pc uintptr
  22. s string
  23. }
  24. func (it *memReportItem) String() string {
  25. more := it.s
  26. if more != "" {
  27. a := strings.Split(more, "\n")
  28. more = "\n\t\t" + strings.Join(a, "\n\t\t")
  29. }
  30. return fmt.Sprintf("\t%s: %#x%s", pc2origin(it.pc), it.p, more)
  31. }
  32. type memReport []memReportItem
  33. func (r memReport) Error() string {
  34. a := []string{"memory leaks"}
  35. for _, v := range r {
  36. a = append(a, v.String())
  37. }
  38. return strings.Join(a, "\n")
  39. }
  40. var (
  41. allocator memory.Allocator
  42. allocs map[uintptr]uintptr // addr: caller
  43. allocsMore map[uintptr]string
  44. frees map[uintptr]uintptr // addr: caller
  45. memAudit memReport
  46. memAuditEnabled bool
  47. )
  48. func pc2origin(pc uintptr) string {
  49. f := runtime.FuncForPC(pc)
  50. var fn, fns string
  51. var fl int
  52. if f != nil {
  53. fn, fl = f.FileLine(pc)
  54. fns = f.Name()
  55. if x := strings.LastIndex(fns, "."); x > 0 {
  56. fns = fns[x+1:]
  57. }
  58. }
  59. return fmt.Sprintf("%s:%d:%s", fn, fl, fns)
  60. }
  61. // void *malloc(size_t size);
  62. func Xmalloc(t *TLS, size types.Size_t) uintptr {
  63. if __ccgo_strace {
  64. trc("t=%v size=%v, (%v:)", t, size, origin(2))
  65. }
  66. if size == 0 {
  67. // malloc(0) should return unique pointers
  68. // (often expected and gnulib replaces malloc if malloc(0) returns 0)
  69. size = 1
  70. }
  71. allocMu.Lock()
  72. defer allocMu.Unlock()
  73. p, err := allocator.UintptrCalloc(int(size))
  74. // if dmesgs {
  75. // dmesg("%v: %v -> %#x, %v", origin(1), size, p, err)
  76. // }
  77. if err != nil {
  78. t.setErrno(errno.ENOMEM)
  79. return 0
  80. }
  81. if memAuditEnabled {
  82. pc, _, _, ok := runtime.Caller(1)
  83. if !ok {
  84. panic("cannot obtain caller's PC")
  85. }
  86. delete(frees, p)
  87. if pc0, ok := allocs[p]; ok {
  88. dmesg("%v: malloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0))
  89. panic(fmt.Errorf("%v: malloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0)))
  90. }
  91. allocs[p] = pc
  92. }
  93. return p
  94. }
  95. // void *calloc(size_t nmemb, size_t size);
  96. func Xcalloc(t *TLS, n, size types.Size_t) uintptr {
  97. if __ccgo_strace {
  98. trc("t=%v n=%v size=%v, (%v:)", t, n, size, origin(2))
  99. }
  100. rq := int(n * size)
  101. if rq == 0 {
  102. rq = 1
  103. }
  104. allocMu.Lock()
  105. defer allocMu.Unlock()
  106. p, err := allocator.UintptrCalloc(rq)
  107. // if dmesgs {
  108. // dmesg("%v: %v -> %#x, %v", origin(1), n*size, p, err)
  109. // }
  110. if err != nil {
  111. t.setErrno(errno.ENOMEM)
  112. return 0
  113. }
  114. if memAuditEnabled {
  115. pc, _, _, ok := runtime.Caller(1)
  116. if !ok {
  117. panic("cannot obtain caller's PC")
  118. }
  119. delete(frees, p)
  120. if pc0, ok := allocs[p]; ok {
  121. dmesg("%v: calloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0))
  122. panic(fmt.Errorf("%v: calloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0)))
  123. }
  124. allocs[p] = pc
  125. }
  126. return p
  127. }
  128. // void *realloc(void *ptr, size_t size);
  129. func Xrealloc(t *TLS, ptr uintptr, size types.Size_t) uintptr {
  130. if __ccgo_strace {
  131. trc("t=%v ptr=%v size=%v, (%v:)", t, ptr, size, origin(2))
  132. }
  133. allocMu.Lock()
  134. defer allocMu.Unlock()
  135. var pc uintptr
  136. if memAuditEnabled {
  137. var ok bool
  138. if pc, _, _, ok = runtime.Caller(1); !ok {
  139. panic("cannot obtain caller's PC")
  140. }
  141. if ptr != 0 {
  142. if pc0, ok := frees[ptr]; ok {
  143. dmesg("%v: realloc: double free of %#x, previous call at %v:", pc2origin(pc), ptr, pc2origin(pc0))
  144. panic(fmt.Errorf("%v: realloc: double free of %#x, previous call at %v:", pc2origin(pc), ptr, pc2origin(pc0)))
  145. }
  146. if _, ok := allocs[ptr]; !ok {
  147. dmesg("%v: %v: realloc, free of unallocated memory: %#x", origin(1), pc2origin(pc), ptr)
  148. panic(fmt.Errorf("%v: realloc, free of unallocated memory: %#x", pc2origin(pc), ptr))
  149. }
  150. delete(allocs, ptr)
  151. delete(allocsMore, ptr)
  152. frees[ptr] = pc
  153. }
  154. }
  155. p, err := allocator.UintptrRealloc(ptr, int(size))
  156. // if dmesgs {
  157. // dmesg("%v: %#x, %v -> %#x, %v", origin(1), ptr, size, p, err)
  158. // }
  159. if err != nil {
  160. t.setErrno(errno.ENOMEM)
  161. return 0
  162. }
  163. if memAuditEnabled && p != 0 {
  164. delete(frees, p)
  165. if pc0, ok := allocs[p]; ok {
  166. dmesg("%v: realloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0))
  167. panic(fmt.Errorf("%v: realloc returns same address twice, previous call at %v:", pc2origin(pc), pc2origin(pc0)))
  168. }
  169. allocs[p] = pc
  170. }
  171. return p
  172. }
  173. // void free(void *ptr);
  174. func Xfree(t *TLS, p uintptr) {
  175. if __ccgo_strace {
  176. trc("t=%v p=%v, (%v:)", t, p, origin(2))
  177. }
  178. if p == 0 {
  179. return
  180. }
  181. // if dmesgs {
  182. // dmesg("%v: %#x", origin(1), p)
  183. // }
  184. allocMu.Lock()
  185. defer allocMu.Unlock()
  186. sz := memory.UintptrUsableSize(p)
  187. if memAuditEnabled {
  188. pc, _, _, ok := runtime.Caller(1)
  189. if !ok {
  190. panic("cannot obtain caller's PC")
  191. }
  192. if pc0, ok := frees[p]; ok {
  193. dmesg("%v: double free of %#x, previous call at %v:", pc2origin(pc), p, pc2origin(pc0))
  194. panic(fmt.Errorf("%v: double free of %#x, previous call at %v:", pc2origin(pc), p, pc2origin(pc0)))
  195. }
  196. if _, ok := allocs[p]; !ok {
  197. dmesg("%v: free of unallocated memory: %#x", pc2origin(pc), p)
  198. panic(fmt.Errorf("%v: free of unallocated memory: %#x", pc2origin(pc), p))
  199. }
  200. delete(allocs, p)
  201. delete(allocsMore, p)
  202. frees[p] = pc
  203. }
  204. for i := uintptr(0); i < uintptr(sz); i++ {
  205. *(*byte)(unsafe.Pointer(p + i)) = 0
  206. }
  207. allocator.UintptrFree(p)
  208. }
  209. func UsableSize(p uintptr) types.Size_t {
  210. allocMu.Lock()
  211. defer allocMu.Unlock()
  212. if memAuditEnabled {
  213. pc, _, _, ok := runtime.Caller(1)
  214. if !ok {
  215. panic("cannot obtain caller's PC")
  216. }
  217. if _, ok := allocs[p]; !ok {
  218. dmesg("%v: usable size of unallocated memory: %#x", pc2origin(pc), p)
  219. panic(fmt.Errorf("%v: usable size of unallocated memory: %#x", pc2origin(pc), p))
  220. }
  221. }
  222. return types.Size_t(memory.UintptrUsableSize(p))
  223. }
  224. func Xmalloc_usable_size(tls *TLS, p uintptr) (r Tsize_t) {
  225. return UsableSize(p)
  226. }
  227. type MemAllocatorStat struct {
  228. Allocs int
  229. Bytes int
  230. Mmaps int
  231. }
  232. // MemStat no-op for this build tag
  233. func MemStat() MemAllocatorStat {
  234. return MemAllocatorStat{}
  235. }
  236. // MemAuditStart locks the memory allocator, initializes and enables memory
  237. // auditing. Finally it unlocks the memory allocator.
  238. //
  239. // Some memory handling errors, like double free or freeing of unallocated
  240. // memory, will panic when memory auditing is enabled.
  241. //
  242. // This memory auditing functionality has to be enabled using the libc.memgrind
  243. // build tag.
  244. //
  245. // It is intended only for debug/test builds. It slows down memory allocation
  246. // routines and it has additional memory costs.
  247. func MemAuditStart() {
  248. allocMu.Lock()
  249. defer allocMu.Unlock()
  250. allocs = map[uintptr]uintptr{} // addr: caller
  251. allocsMore = map[uintptr]string{}
  252. frees = map[uintptr]uintptr{} // addr: caller
  253. memAuditEnabled = true
  254. }
  255. // MemAuditReport locks the memory allocator, reports memory leaks, if any.
  256. // Finally it disables memory auditing and unlocks the memory allocator.
  257. //
  258. // This memory auditing functionality has to be enabled using the libc.memgrind
  259. // build tag.
  260. //
  261. // It is intended only for debug/test builds. It slows down memory allocation
  262. // routines and it has additional memory costs.
  263. func MemAuditReport() (r error) {
  264. allocMu.Lock()
  265. defer func() {
  266. allocs = nil
  267. allocsMore = nil
  268. frees = nil
  269. memAuditEnabled = false
  270. memAudit = nil
  271. allocMu.Unlock()
  272. }()
  273. if len(allocs) != 0 {
  274. for p, pc := range allocs {
  275. memAudit = append(memAudit, memReportItem{p, pc, allocsMore[p]})
  276. }
  277. sort.Slice(memAudit, func(i, j int) bool {
  278. return memAudit[i].String() < memAudit[j].String()
  279. })
  280. return memAudit
  281. }
  282. return nil
  283. }
  284. func MemAuditAnnotate(pc uintptr, s string) {
  285. allocMu.Lock()
  286. allocsMore[pc] = s
  287. allocMu.Unlock()
  288. }