#include <pthread.h>
#include <unistd.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include <cstdio>
#include <cstdarg>
#include <cerrno>
#include <cstdlib>
#include <malloc/malloc.h>
#include "CFSoftLinking.h"
#include "AutoBlockIterator.h"
#include "AutoDefs.h"
#include "AutoEnvironment.h"
#include "AutoMonitor.h"
#include "AutoMemoryScanner.h"
#include "AutoRootScanner.h"
#include "AutoZone.h"
#include "auto_zone.h"
namespace Auto {
const char* kPropertyListXMLHeader = "<plist version=\"1.0\">\n<dict>\n";
const char* kPropertyListXMLFooter = "</dict>\n</plist>\n";
Monitor *Monitor::_monitor;
Monitor *Monitor::monitor() {
if (Environment::_agc_env._enable_monitor && !_monitor) {
_monitor = new Monitor();
}
return _monitor;
}
int (*Monitor::_class_list)(void **buffer, int count);
ptr_set *Monitor::_class_set;
int Monitor::_class_count;
void Monitor::set_class_list(int (*class_list)(void **buffer, int count)) {
_class_list = class_list;
_class_set = ptr_set_new();
_class_count = 0;
}
struct objc_class_header {
struct objc_class *isa;
struct objc_class *super_class;
const char *name;
long version;
long info;
long instance_size;
};
bool Monitor::is_object(void *ptr, long size) {
if (_class_list) {
int count = _class_list(NULL, 0);
if (count > _class_count) {
void **buffer = (void**) aux_malloc(count * sizeof(void*));
int new_count = _class_list(buffer, count);
while (new_count > count) {
count = new_count;
buffer = (void**) aux_realloc(buffer, count * sizeof(void*));
new_count = _class_list(buffer, count);
}
_class_count = count;
for (int i = 0; i < count; i++) ptr_set_add(_class_set, buffer[i]);
aux_free(buffer);
}
objc_class_header *isa = *(objc_class_header**)ptr;
return isa && ptr_set_is_member(_class_set, isa) && (size >= isa->instance_size);
}
return false;
}
Monitor::Monitor() : _argc(0) {
nano_time();
}
void Monitor::open_mach_port() {
CFStringRef format = CFStringCreateWithCString(NULL, "com.apple.auto.%d", kCFStringEncodingUTF8);
CFStringRef name = CFStringCreateWithFormat(NULL, NULL, format, getpid());
CFRelease(format);
CFMessagePortContext context = { 0, this, NULL, NULL, NULL };
_request_port = CFMessagePortCreateLocal(NULL, name, receive_request, &context, NULL);
CFRelease(name);
CFRunLoopSourceRef source = CFMessagePortCreateRunLoopSource(NULL, _request_port, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
CFRelease(source);
CFRunLoopRun();
}
void Monitor::print(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
char *buffer;
int length = vasprintf(&buffer, fmt, ap);
if (buffer) {
CFDataAppendBytes(_response_buffer, (const UInt8*)buffer, length);
free(buffer);
}
va_end(ap);
}
void Monitor::tokenize_args() {
char *cursor = _request;
for (_argc = 0; *cursor && _argc < max_args; _argc++) {
while (*cursor <= ' ' && *cursor != '\0') *cursor++ = '\0';
if (*cursor == '\0') break;
if (*cursor == '\"') {
cursor++;
_args[_argc] = cursor;
while (*cursor >= ' ') {
if (*cursor == '\"') {
*cursor++ = '\0';
break;
}
cursor++;
}
} else {
_args[_argc] = cursor;
while (*cursor > ' ') cursor++;
}
}
}
void Monitor::process_request() {
if (_argc > 0) {
char *command = _args[0];
if (is_equal(command, "blocks")) { send_all_blocks(); return; }
if (is_equal(command, "rootblocks")) { send_root_blocks(); return; }
if (is_equal(command, "content")) { send_block_content(); return; }
if (is_equal(command, "describe")) { send_block_description(); return; }
if (is_equal(command, "leaks")) { send_leaks(); return; }
if (is_equal(command, "references")) { send_references(); return; }
if (is_equal(command, "roots")) { send_roots(); return; }
if (is_equal(command, "samples")) { send_zone_samples(); return; }
if (is_equal(command, "samplesAll")) { send_process_samples(); return; }
if (is_equal(command, "zones")) { send_all_zones(); return; }
print("Unknown command %s\n", command);
}
}
CFDataRef Monitor::receive_request(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info) {
Monitor *monitor = (Monitor *)info;
CFIndex length = CFDataGetLength(data);
memcpy(monitor->_request, CFDataGetBytePtr(data), length);
CFDataRef reply = monitor->_response_buffer = CFDataCreateMutable(kCFAllocatorMallocZone, 0);
monitor->_stack_bottom = (void*) auto_get_sp();
monitor->tokenize_args();
Zone *zone = Zone::zone();
if (zone) zone->block_collector();
monitor->process_request();
if (zone) zone->unblock_collector();
CFDataAppendBytes(monitor->_response_buffer, (const UInt8*)"\0", 1);
monitor->_response_buffer = NULL;
return reply;
}
struct send_all_blocks_visitor {
Monitor *_monitor;
send_all_blocks_visitor(Monitor *monitor) : _monitor(monitor) {}
inline bool visit(Zone *zone, Subzone *subzone, usword_t q) {
_monitor->send_block(zone, subzone, q, subzone->quantum_address(q));
return true;
}
inline bool visit(Zone *zone, Large *large) {
_monitor->send_block(zone, large, large->address());
return true;
}
};
static void malloc_block_recorder(task_t task, void *context, unsigned type, vm_range_t *range, unsigned count) {
Monitor *monitor = reinterpret_cast<Monitor*>(context);
for (unsigned i = 0; i < count; i++, range++) {
monitor->print("block");
monitor->send_malloc_block_info((void*)range->address, range->size);
monitor->print("\n");
}
}
void Monitor::send_all_blocks() {
malloc_zone_t *zone = (malloc_zone_t *)strtoul(_args[2], NULL, 0);
print("blocks %s\n", _args[1]);
if (zone == (malloc_zone_t *)Zone::zone()) {
send_all_blocks_visitor visitor(this);
visitAllocatedBlocks(reinterpret_cast<Zone*>(zone), visitor);
} else {
zone->introspect->enumerator(mach_task_self(), this, MALLOC_PTR_IN_USE_RANGE_TYPE, (vm_address_t) zone, NULL, malloc_block_recorder);
}
print("\\blocks\n");
}
void Monitor::send_block_info(Zone *zone, void *block) {
if (zone->in_subzone_memory(block)) {
Subzone *subzone = Subzone::subzone(block);
return send_block_info(zone, subzone, subzone->quantum_index(block), block);
} else if (zone->in_large_memory(block)) {
return send_block_info(zone, Large::large(block), block);
} else {
ASSERTION(0 && "not a block");
}
}
void Monitor::send_block_info(Zone *zone, Subzone *subzone, usword_t q, void *block) {
int rc = zone->block_refcount(block);
int layout = subzone->layout(q);
bool is_unscanned = (layout & AUTO_UNSCANNED) != 0;
bool is_object = (layout & AUTO_OBJECT) != 0;
bool is_new = subzone->is_new(q);
bool is_marked = subzone->is_marked(q);
char *class_name = NULL;
if (is_object) {
class_name = zone->control.name_for_address((auto_zone_t *)zone, (vm_address_t)block, 0);
}
print(" %p %lu %d %s%s%s%s %s",
block, (unsigned long)subzone->size(q),
rc,
is_unscanned ? "u" : "s",
is_object ? "o" : "m",
is_new ? "n" : "o",
is_marked ? "m" : "u",
class_name ? class_name : "");
if (class_name) free(class_name);
}
void Monitor::send_block_info(Zone *zone, Large *large, void *block) {
int rc = zone->block_refcount(block);
int layout = large->layout();
bool is_unscanned = (layout & AUTO_UNSCANNED) != 0;
bool is_object = (layout & AUTO_OBJECT) != 0;
bool is_new = large->is_new();
bool is_marked = large->is_marked();
char *class_name = NULL;
if (is_object) {
class_name = zone->control.name_for_address((auto_zone_t *)zone, (vm_address_t)block, 0);
}
print(" %p %lu %d %s%s%s%s %s",
block, (unsigned long)large->size(),
rc,
is_unscanned ? "u" : "s",
is_object ? "o" : "m",
is_new ? "n" : "o",
is_marked ? "m" : "u",
class_name ? class_name : "");
if (class_name) free(class_name);
}
void Monitor::send_malloc_block_info(void *block, size_t size) {
int rc = 1;
int layout = AUTO_MEMORY_UNSCANNED;
bool is_unscanned = (layout & AUTO_UNSCANNED) != 0;
bool is_new = false;
bool is_marked = false;
const char *class_name = "";
bool is_object = Monitor::is_object(block, size);
if (is_object) class_name = (*(objc_class_header**)block)->name;
print(" %p %lu %d %s%s%s%s %s",
block, size,
rc,
is_unscanned ? "u" : "s",
is_object ? "o" : "m",
is_new ? "n" : "o",
is_marked ? "m" : "u",
class_name);
}
void Monitor::send_block_content() {
malloc_zone_t *zone = (malloc_zone_t *)strtoul(_args[2], NULL, 0);
void *block = (void *)strtoul(_args[3], NULL, 0);
print("content %s\n", _args[1]);
if (zone == (malloc_zone_t *)Zone::zone()) {
Zone *azone = (Zone *)zone;
if (azone->is_block(block)) {
usword_t size = azone->block_size(block);
for (usword_t offset = 0; offset < size; offset += sizeof(void *)) {
intptr_t *slot = (intptr_t *)displace(block, offset);
intptr_t content = *slot;
print("slot %p %lu %p", slot, offset, content);
if (azone->is_block((void *)content)) {
send_block_info(azone, (void *)content);
} else {
}
print("\n");
}
}
} else {
size_t size = malloc_size(block);
if (size != 0) {
for (usword_t offset = 0; offset < size; offset += sizeof(void *)) {
intptr_t *slot = (intptr_t *)displace(block, offset);
intptr_t content = *slot;
print("slot %p %lu %p", slot, offset, content);
size_t content_size = malloc_size((void *)content);
if (content_size) {
send_malloc_block_info((void *)content, content_size);
} else {
}
print("\n");
}
}
}
print("\\content\n");
}
void Monitor::send_block_description() {
print(kPropertyListXMLHeader);
malloc_zone_t *zone = (malloc_zone_t *)strtoul(_args[2], NULL, 0);
void *block = (void *)strtoul(_args[3], NULL, 0);
print("<key>requestor</key><string>%s</string>\n", _args[1]);
print("<key>block</key><string>%s</string>\n", _args[3]);
if (zone == (malloc_zone_t *)Zone::zone()) {
Zone *azone = (Zone *)zone;
if (azone->is_block(block)) {
auto_memory_type_t type = auto_zone_get_layout_type(zone, block);
if ((type & AUTO_OBJECT) == AUTO_OBJECT) {
CFStringRef description = CFCopyDescription((CFTypeRef)block);
if (description) {
CFStringRef escaped = CFXMLCreateStringByEscapingEntities(NULL, description, NULL);
if (escaped != description) {
CFRelease(description);
description = escaped;
}
char buffer[CFStringGetMaximumSizeForEncoding(CFStringGetLength(description), kCFStringEncodingUTF8)];
CFStringGetCString(description, buffer, sizeof(buffer), kCFStringEncodingUTF8);
CFRelease(description);
print("<key>description</key><string>%s</string>", buffer);
}
}
}
}
print(kPropertyListXMLFooter);
}
void Monitor::send_block(Zone *zone, Subzone *subzone, usword_t q, void *block) {
print("block");
send_block_info(zone, subzone, q, block);
print("\n");
}
void Monitor::send_block(Zone *zone, Large *large, void *block) {
print("block");
send_block_info(zone, large, block);
print("\n");
}
void Monitor::send_all_zones() {
Zone *zone = Zone::zone();
print("zones %s\n", _args[1]);
if (zone) {
print("zone %p %p %s\n", zone, zone, malloc_get_zone_name((malloc_zone_t *)zone));
}
vm_address_t *zone_addresses;
unsigned count = 0;
malloc_get_all_zones(mach_task_self(), NULL, &zone_addresses, &count);
for (unsigned i = 0; i < count; i++) {
malloc_zone_t *malloc_zone = (malloc_zone_t *)zone_addresses[i];
if (malloc_zone != (malloc_zone_t *)zone) print("zone %p 0x00000000 \"%s\"\n", malloc_zone, malloc_get_zone_name(malloc_zone));
}
print("\\zones\n");
}
struct LeakScanner : public MemoryScanner {
LeakScanner(Zone *zone, void *stack_bottom)
: MemoryScanner(zone, stack_bottom, false, false)
{}
virtual void scan_retained_blocks() {
}
};
struct send_leaks_visitor {
Monitor *_monitor;
send_leaks_visitor(Monitor *monitor) : _monitor(monitor) {}
inline bool visit(Zone *zone, Subzone *subzone, usword_t q) {
if (!subzone->is_marked(q) && subzone->has_refcount(q)) {
_monitor->send_block(zone, subzone, q, subzone->quantum_address(q));
}
return true;
}
inline bool visit(Zone *zone, Large *large) {
if (!large->is_marked() && large->refcount()) {
_monitor->send_block(zone, large, large->address());
}
return true;
}
};
void Monitor::send_leaks() {
Zone *zone = (Zone *)strtoul(_args[2], NULL, 0);
LeakScanner scanner(zone, _stack_bottom);
scanner.scan();
print("leaks %s\n", _args[1]);
send_leaks_visitor visitor(this);
BlockIterator<send_leaks_visitor> iterator(zone, visitor);
iterator.visit();
print("\\leaks\n");
zone->reset_all_marks_and_pending();
}
struct ReferenceScanner : public MemoryScanner {
Monitor *_monitor; void *_block; Thread *_thread; int _first_register; Range _thread_range;
ReferenceScanner(Zone *zone, void *block, Monitor *monitor, void* stack_bottom)
: MemoryScanner(zone, stack_bottom, false, true)
, _monitor(monitor)
, _block(block)
, _thread(NULL)
, _first_register(-1)
, _thread_range()
{
}
virtual void check_block(void **reference, void *block) {
MemoryScanner::check_block(reference, block);
if (block == _block) {
if (_thread) {
intptr_t offset = (intptr_t)reference - (intptr_t)_thread_range.end();
if (_first_register != -1) {
int id = (offset >> 2) + _first_register;
_monitor->print("reference %p %d r %d r%d", reference, offset, id, id);
} else {
_monitor->print("reference %p %d t %p \"thread stack\"", reference, offset, _thread_range.end());
}
} else if (!reference) {
_monitor->print("reference 0 0 z 0 \"zone retained\"");
} else {
void *owner = _zone->block_start((void*)reference);
if (owner) {
intptr_t offset = (intptr_t)reference - (intptr_t)owner;
char *referrer_name = _zone->control.name_for_address((auto_zone_t *)_zone, (vm_address_t)owner, (vm_address_t)offset);
_monitor->print("reference %p %d b %p %s", reference, offset, owner, referrer_name);
free(referrer_name);
_monitor->send_block_info(_zone, (void *)owner);
} else if (_zone->is_root(reference)) {
Dl_info info;
if (dladdr(reference, &info) != 0 && info.dli_saddr == reference)
_monitor->print("reference %p 0 b 0 \"global variable: %s\"", reference, info.dli_sname);
else
_monitor->print("reference %p 0 b 0 \"registered root\"", reference);
} else {
_monitor->print("reference %p 0 b 0 \"unknown container\"", reference);
}
}
_monitor->print("\n");
}
}
void scan_range_from_thread(Range &range, Thread *thread) {
_thread = thread;
_thread_range = range;
MemoryScanner::scan_range_from_thread(range, thread);
_thread = NULL;
_thread_range = Range();
}
void scan_range_from_registers(Range &range, Thread *thread, int first_register) {
_thread = thread;
_first_register = first_register;
_thread_range = range;
MemoryScanner::scan_range_from_registers(range, thread, first_register);
_thread = NULL;
_first_register = -1;
}
};
void Monitor::send_references() {
Zone *zone = (Zone *)strtoul(_args[2], NULL, 0);
void *block = (void *)strtoul(_args[3], NULL, 0);
ReferenceScanner scanner(zone, block, this, _stack_bottom);
print("references %s\n", _args[1]);
scanner.scan();
zone->reset_all_marks_and_pending();
print("\\references\n");
}
struct MonitorRootScanner : public RootScanner {
Monitor *_monitor;
MonitorRootScanner(Zone *zone, void *block, Monitor *monitor, void* stack_bottom)
: RootScanner(zone, block, stack_bottom), _monitor(monitor)
{
}
void print_root(ReferenceNode *node, ReferenceNode *nextNode) {
void *address = node->address();
switch (node->_kind) {
case ReferenceNode::HEAP:
usword_t offset = node->offsetOf(nextNode);
char *referrer_name = _zone->control.name_for_address((auto_zone_t *)_zone, (vm_address_t)address, offset);
_monitor->print("reference %p %u b %p %s", (uintptr_t)address + offset, offset, address, referrer_name);
free(referrer_name);
_monitor->send_block_info(_zone, address);
break;
case ReferenceNode::ROOT:
Dl_info info;
if (dladdr(address, &info) != 0 && info.dli_saddr == address)
_monitor->print("reference %p 0 b 0 \"global variable: %s\"", address, info.dli_sname);
else
_monitor->print("reference %p 0 b 0 \"registered root\"", address);
break;
case ReferenceNode::STACK:
_monitor->print("reference %p %ld t %p \"thread stack\"", address, -(intptr_t)node->size(), node->end());
break;
}
_monitor->print("\n");
}
void print_roots(void *block) {
usword_t count = _graph._nodes.length();
for (usword_t i = 0; i < count; ++i) {
ReferenceNode& node = _graph._nodes[i];
void *address = node.address();
if (node._kind == ReferenceNode::STACK || node._kind == ReferenceNode::ROOT || (_zone->is_block(address) && _zone->block_refcount(address) > 0)) {
List<ReferenceNode*> path;
if (_graph.findPath(node.address(), block, path)) {
usword_t length = path.length();
for (usword_t j = 1; j <= length; ++j) {
ReferenceNode* currentNode = path[length - j];
ReferenceNode* nextNode = (j < length ? path[length - j - 1] : NULL);
print_root(currentNode, nextNode);
}
_monitor->print("\n");
}
_graph.resetNodes();
}
}
}
};
void Monitor::send_roots() {
Zone *zone = (Zone *)strtoul(_args[2], NULL, 0);
void *block = (void *)strtoul(_args[3], NULL, 0);
MonitorRootScanner scanner(zone, block, this, _stack_bottom);
zone->clear_use_pending();
ScanStack &scan_stack = zone->scan_stack();
do {
scanner.scan();
zone->reset_all_marks();
} while (scanner.has_pending_blocks());
zone->set_use_pending();
bool stack_overflow = scan_stack.is_overflow();
scan_stack.reset();
print("roots %s\n", _args[1]);
if (!stack_overflow) scanner.print_roots(block);
print("\\roots\n");
}
struct RootFinder : private MemoryScanner {
Monitor *_monitor; bool _scanning_roots;
RangeList _list;
RootFinder(Zone *zone, Monitor *monitor)
: MemoryScanner(zone, NULL, false, true),
_monitor(monitor)
{
}
void find() {
_scanning_roots = true;
scan_root_ranges(); _scanning_roots = false;
scan_retained_blocks(); scan_pending_blocks(); }
virtual void check_block(void **reference, void *block) {
if (_scanning_roots || _zone->block_refcount(block) != 0) {
auto_memory_type_t type = (auto_memory_type_t) _zone->block_layout(block);
if ((type & AUTO_UNSCANNED) != AUTO_UNSCANNED) {
_list.add(Range(block, _zone->block_size(block)));
}
}
}
};
struct BlockScanner : public MemoryScanner {
Range _blockRange;
usword_t _bytesReachable;
usword_t _objectsReachable;
BlockScanner(Zone *zone, Range block)
: MemoryScanner(zone, NULL, false, true), _blockRange(block), _bytesReachable(block.size()), _objectsReachable(0)
{
}
void scan() {
scan_range(_blockRange);
scan_pending_until_done();
}
virtual void check_block(void **reference, void *block) {
_bytesReachable += _zone->block_size(block), ++_objectsReachable;
MemoryScanner::check_block(reference, block); }
};
void Monitor::send_root_blocks() {
Zone *zone = Zone::zone();
zone->set_use_pending();
RootFinder roots(zone, this);
roots.find();
print(kPropertyListXMLHeader);
print("<key>requestor</key><string>%s</string>\n", _args[1]);
print("<key>rootBlocks</key><dict>\n");
for (usword_t i = 0; i < roots._list.length(); ++i) {
BlockScanner scanner(zone, roots._list[i]);
scanner.scan();
_monitor->print("<key>%p</key><array><integer>%lu</integer><integer>%lu</integer></array>\n",
scanner._blockRange.address(), scanner._bytesReachable, scanner._objectsReachable);
}
zone->reset_all_marks_and_pending();
print("</dict>\n");
print(kPropertyListXMLFooter);
}
void Monitor::send_zone_samples() {
malloc_zone_t *malloc_zone = (malloc_zone_t *)strtoul(_args[2], NULL, 0);
malloc_statistics_t stats;
malloc_zone_statistics(malloc_zone, &stats);
print("samples %s\n", _args[1]);
print("sample %f %u %zu %zu %zu\n", nano_time(), stats.blocks_in_use, stats.size_in_use, stats.max_size_in_use, stats.size_allocated);
print("\\samples\n");
}
void Monitor::send_process_samples() {
vm_address_t *zone_addresses;
unsigned count = 0;
malloc_get_all_zones(mach_task_self(), NULL, &zone_addresses, &count);
malloc_statistics_t stats;
bzero(&stats, sizeof(malloc_statistics_t));
for (unsigned i = 0; i < count; i++) {
malloc_zone_t *malloc_zone = (malloc_zone_t *)zone_addresses[i];
malloc_statistics_t zone_stats;
malloc_zone_statistics(malloc_zone, &zone_stats);
stats.blocks_in_use += zone_stats.blocks_in_use;
stats.size_in_use += zone_stats.size_in_use;
stats.max_size_in_use += zone_stats.max_size_in_use;
stats.size_allocated += zone_stats.size_allocated;;
}
print("samples %s\n", _args[1]);
print("sample %f %u %zu %zu %zu\n", nano_time(), stats.blocks_in_use, stats.size_in_use, stats.max_size_in_use, stats.size_allocated);
print("\\samples\n");
}
};