Программно получить все переменные кадра в objective-c

Я пытаюсь создать свой собственный пользовательский assert. Однако я хотел бы, чтобы мое утверждение автоматически включало все соответствующие переменные. Это кажется мне действительно основным, и я искал около часа, но я не могу найти способ получить доступ ко всем соответствующим переменным фрейма стека. Кто-нибудь знает, как получить эти переменные?

FYI - мне не нужно обращаться к переменным в отладчике, мне нужно обращаться к ним программно. Я хотел бы загрузить их вместе с сообщением о сбое, чтобы дать мне больше информации о сбое. Я также знаю, что я могу распечатать их вручную... это именно то, чего я хочу избежать.

3 ответа

Вы в основном просите заново изобрести хороший размер отладчика.

Без символов нет ничего, что можно было бы опросить, чтобы выяснить макет локального фрейма. Даже с символами весьма вероятно, что оптимизатор погрязнет в любых локальных переменных, поскольку оптимизатор будет повторно использовать слоты стека по своему усмотрению, как только он определит, что переменная больше не нужна в кадре.

Обратите внимание, что многие сбои не могут быть пойманы вообще или, если они пойманы, кадр, в котором они произошли, будет уже давно уничтожен.

Поскольку вы упоминаете, что вы создаете настраиваемое утверждение, это звучит так, будто вы на самом деле не хотите, чтобы интроспекция происходила с ошибками, а также дамп привязки локального фрейма, когда вы программно обнаруживаете, что вещи ушли с рельсов. Хотя на самом деле нет возможности автоматически сообщать о состоянии локального стека, вы можете сделать что-то вроде:

{ ... some function ....
 ... local variables ...
 #define reportblock ^{ ... code that summarizes locals ... ; return summary; }
 YourAssert( cond, "cond gone bad. summary: %@", reportblock());
}

Обратите внимание, что #define гарантирует, что каждый объект YourAssert() захватывает состояние во время утверждения. Также обратите внимание, что вышеупомянутое может иметь потенциально существенное влияние на производительность.

Обратите внимание, что я только что сделал этот код. Похоже, что он заслуживает расследования, но может оказаться нежизнеспособным по ряду причин.


Я решил добавить это как отдельный ответ, поскольку использует тот же подход, что и мой другой, но на этот раз со всем кодом ObjC. К сожалению, вам все равно придется повторно объявить все ваши переменные стека, как и раньше, но, надеюсь, теперь он будет работать лучше с вашей существующей базой кода.

StackVariable.h

#import <foundation foundation.h="">
#define LOCAL_VAR(p_type, p_name, p_value)\
 p_type p_name = p_value;\
 StackVariable *__stack_ ## p_name = [[StackVariable alloc] initWithPointer:&p_name\
 size:sizeof(p_type)\
 name:#p_name\
 type:#p_type\
 file:__FILE__\
 line:__LINE__];\
 (void) __stack_ ## p_name;
@interface StackVariable : NSObject
-(id) initWithPointer:(void *) ptr
 size:(size_t) size
 name:(const char *) name
 type:(const char *) type
 file:(const char *) file
 line:(const int) line;
+(NSString *) dump;
@end
</foundation>

StackVariable.m:

#import "StackVariable.h"
static NSMutableArray *stackVariables;
@implementation StackVariable {
 void *_ptr;
 size_t _size;
 const char *_name;
 const char *_type;
 const char *_file;
 int _line;
}
-(id) initWithPointer:(void *)ptr size:(size_t)size name:(const char *)name type:(const char *)type file:(const char *)file line:(int)line
{
 if (self = [super init]) {
 if (stackVariables == nil) {
 stackVariables = [NSMutableArray new];
 }
 _ptr = ptr;
 _size = size;
 _name = name;
 _type = type;
 _file = file;
 _line = line;
 [stackVariables addObject:[NSValue valueWithNonretainedObject:self]];
 }
 return self;
}
-(NSString *) description {
 NSMutableString *result = [NSMutableString stringWithFormat:@"%s:%d - %s %s = { ", _file, _line, _type, _name];
 const ******* *bytes = (const ***** *) _ptr;
 for (size_t i = 0; i < _size; i++) {
 [result appendFormat:@"%02x ", bytes[i]];
 }
 [result appendString:@"}"];
 return result;
}
+(NSString *) dump {
 NSMutableString *result = [NSMutableString new];
 for (NSValue *value in stackVariables) {
 __weak StackVariable *var = [value nonretainedObjectValue];
 [result appendString:[var description]];
 [result appendString:@"\n"];
 }
 return result;
}
-(void) dealloc {
 [stackVariables removeObject:[NSValue valueWithNonretainedObject:self]];
}
@end

Пример:

#include "StackVariable.h"
int temp() {
 LOCAL_VAR(int, i_wont_show, 0);
 return i_wont_show;
}
int main(){
 LOCAL_VAR(long, l, 15);
 LOCAL_VAR(int, x, 192);
 LOCAL_VAR(short, y, 256);
 temp();
 l += 10;
 puts([[StackVariable dump] UTF8String]);
}

Вывод:

/Users/rross/Documents/TestProj/TestProj/main.m:676 - long l = { 19 00 00 00 00 00 00 00 }
/Users/rross/Documents/TestProj/TestProj/main.m:677 - int x = { c0 00 00 00 }
/Users/rross/Documents/TestProj/TestProj/main.m:678 - short y = { 00 01 }

Для этого требуется ARC (и все это волшебство) для любого файла, который вы хотите протестировать, или вам придется вручную вывести переменные __stack_, которые не будут хороши.

Однако теперь он дает вам шестнадцатеричный дамп переменной (а не странный указатель), и если вы действительно достаточно старались (используя __builtin_types_compatible), он мог определить, был ли результат объектом, и распечатать что.

Опять же, потоки испортили бы это, но простой способ исправить это было бы создать NSDictionary of NSArrays с ключом NSThread. Делает это немного медленнее, но пусть честно, если вы используете это по сравнению с версией на С++, вы не собираетесь работать.


Если вы хотите использовать Objective-C ++, то это определенно возможность, если вы также готовы объявить свои переменные по-разному и понимаете, что вы сможете только захватить свои собственные переменные с помощью этот метод.

Также обратите внимание, что это увеличит размер фрейма стека с дополнительными переменными __stack_, что может вызвать проблемы с памятью (хотя я лично сомневаюсь в этом).

Он не будет работать с определенными конструкциями, такими как for-loops, но в 95% случаев это должно работать для того, что вам нужно:

#include <vector>
struct stack_variable;
static std::vector<const stack_variable="" *=""> stack_variables;
struct stack_variable {
 void **_value;
 const char *_name;
 const char *_type;
 const char *_file;
 const char *_line;
private: 
 template<typename t="">
 stack_variable(const T& value, const char *type, const char *name, const char *file, const char *line) : _value((void **) &value), _type(type), _name(name), _file(file), _line(line) {
 add(*this);
 }
 static inline void add(const stack_variable &var) {
 stack_variables.push_back(static_cast<const stack_variable="" *="">(&var));
 }
 static inline void remove(const stack_variable &var) {
 for (auto it = stack_variables.begin(); it != stack_variables.end(); it++) {
 if ((*it) == &var) {
 stack_variables.erase(it);
 return;
 }
 }
 }
public:
 template<typename t="">
 static inline stack_variable create(const T& value, const char *type, const char *name, const char *file, const char *line) {
 return stack_variable(value, type, name, file, line);
 }
 ~stack_variable() {
 remove(*this);
 }
 void print() const {
 // treat the value as a pointer
 printf("%s:%s - %s %s = %p\n", _file, _line, _type, _name, *_value);
 }
 static void dump_vars() {
 for (auto var : stack_variables) {
 var->print();
 }
 }
};
#define __LINE_STR(LINE) #LINE
#define _LINE_STR(LINE) __LINE_STR(LINE)
#define LINE_STR _LINE_STR(__LINE__)
#define LOCAL_VAR(type, name, value)\
type name = value;\
stack_variable __stack_ ## name = stack_variable::create<type>(name, #type, #name, __FILE__, LINE_STR);\
(void) __stack_ ## name;
</type></typename></const></typename></const></vector>

Пример:

int temp() {
 LOCAL_VAR(int, i_wont_show, 0);
 return i_wont_show;
}
int main(){
 LOCAL_VAR(long, l, 15);
 LOCAL_VAR(int, x, 192);
 LOCAL_VAR(short, y, 256);
 temp();
 l += 10;
 stack_variable::dump_vars();
}

Вывод (обратите внимание на ненужные дополнительные байты для значений, меньших, чем sizeof (void *), я не могу этого сделать):

/Users/rross/Documents/TestProj/TestProj/main.mm:672 - long l = 0x19
/Users/rross/Documents/TestProj/TestProj/main.mm:673 - int x = 0x5fbff8b8000000c0
/Users/rross/Documents/TestProj/TestProj/main.mm:674 - short y = 0xd000000010100

Нити будут рояльно испортить это, однако, поэтому в многопоточной среде это (почти) бесполезно.

licensed under cc by-sa 3.0 with attribution.