writting a stack tracer?
writting a stack tracer?
I know most c/c++ functions gets put on the stack or at least when you call them.
Usually call func equals to a push return address , jmp to func.
Usually code does it by Enter which is equivalent to push ebp/bp , mov ebp/bp , esp/sp , sub esp/sp number of bytes to top or add esp/sp n if it grows the other way. ( determine by a bit in the GDT or selector)
I can get the begining of the whole stack by ss selector address but my problem is a few things
1) I can look for the machine code for ENTER and LEAVE ( ENTER imm,imm ; C8 iw ib , LEAVE ; C9) on the stack
But how can I get the names of the functions corrosponding to these stack entries if their is no symbol table ?
2) Typically does OS like linux or windows have a different stack for each program. For example if I can get the begining of the stack will it corrospond to the begining of my program I am analysising, or do the typical OS's (like ms,linux) use the same stack for multiple programs?
The main problem is if it is not the beging of my programming stack then I don't know a way to get the begining of my programming stack or to tell the difference between the other programs using the stacks functions.
If the begining of the ss corrosponds to my programs begining of the stack that would imply the first ENTER machine code I see would be the first function call that my program used.(i.e the first function that the program called after the loader loads and jumps to the starting address)
Anybody know more about how to analysis the stack or write a stack tracer.
I know this thread could cause confusion because I am looking at it from 2 aspects. One I want to know from a program runing under an OS like linux/windows.... and the other way of looking at it is if I was to do a stack tracer with no OS my own in ring 0 with no ntloader type of program as a middle man.
Usually call func equals to a push return address , jmp to func.
Usually code does it by Enter which is equivalent to push ebp/bp , mov ebp/bp , esp/sp , sub esp/sp number of bytes to top or add esp/sp n if it grows the other way. ( determine by a bit in the GDT or selector)
I can get the begining of the whole stack by ss selector address but my problem is a few things
1) I can look for the machine code for ENTER and LEAVE ( ENTER imm,imm ; C8 iw ib , LEAVE ; C9) on the stack
But how can I get the names of the functions corrosponding to these stack entries if their is no symbol table ?
2) Typically does OS like linux or windows have a different stack for each program. For example if I can get the begining of the stack will it corrospond to the begining of my program I am analysising, or do the typical OS's (like ms,linux) use the same stack for multiple programs?
The main problem is if it is not the beging of my programming stack then I don't know a way to get the begining of my programming stack or to tell the difference between the other programs using the stacks functions.
If the begining of the ss corrosponds to my programs begining of the stack that would imply the first ENTER machine code I see would be the first function call that my program used.(i.e the first function that the program called after the loader loads and jumps to the starting address)
Anybody know more about how to analysis the stack or write a stack tracer.
I know this thread could cause confusion because I am looking at it from 2 aspects. One I want to know from a program runing under an OS like linux/windows.... and the other way of looking at it is if I was to do a stack tracer with no OS my own in ring 0 with no ntloader type of program as a middle man.
- Combuster
- Member
- Posts: 9301
- Joined: Wed Oct 18, 2006 3:45 am
- Libera.chat IRC: [com]buster
- Location: On the balcony, where I can actually keep 1½m distance
- Contact:
Re: writting a stack tracer?
Seriously, don't. Stack contains data, not code.I can look for the machine code (...) on the stack
False.Usually code does it by Enter
Re: writting a stack tracer?
No, functions are code and remain in the code 'segment'.I know most c/c++ functions gets put on the stack or at least when you call them.
No, 'enter' is rarely used.Usually code does it by Enter which is equivalent to push ebp/bp ,
That's not true. The stack on x86 always grows down.if it grows the other way. ( determine by a bit in the GDT or selector)
No, the SS selector is almost always the same as the data selector and the stack is only a part of the data 'segment'. You can't tell which part by looking at SS.I can get the begining of the whole stack by ss selector address
That's possibly true....but my problem is a few things
No. You don't really understand the stack.1) I can look for the machine code for ENTER and LEAVE ( ENTER imm,imm ; C8 iw ib , LEAVE ; C9) on the stack
You can't. It's obvious that without a table of symbols you won't have any symbols.But how can I get the names of the functions corrosponding to these stack entries if their is no symbol table ?
Not necessarily, the beginning of the stack often contains things like TLS and argv array.2) Typically does OS like linux or windows have a different stack for each program. For example if I can get the begining of the stack will it corrospond to the begining of my program I am analysising,
Of course not. Every programme has multiple stacks. Enough for each thread and perhaps more stacks for transferring between rings.or do the typical OS's (like ms,linux) use the same stack for multiple programs?
I'm not sure I can help you with your specific questions because your base assumptions are incorrect.
If a trainstation is where trains stop, what is a workstation ?
Re: writting a stack tracer?
Ok, I see my flaw in my logic.
This was stupid of me since I knew most of what I was think about but just ran the other way with it in my mind.
So let me correct myself.
1) cs segment for my program contains the ENTER/LEAVE instructions that are equivalent to push ebp , mov ebp , esp , sub esp , n
It is only the data that the push and pop commands put on the stack obviously not the instructions themself.... unless of course you where pushing shell code on the stack to cause a bufferover flow. So all the instructions are obviously in the code segment and the stack is in the data segment.
( well I will take your word when you say ENTER/LEAVE commands are not common to use in setting up the stack frame thought they where but maybe it was the other 3 statements instead either way you could uses either.... I would pick the fastest instruction )
2) The stack will only contain the data I push onto it from the code segment. But in theory I will have the address of the functions and their parameters from the stack.... at a given instance. (problem is determing what corrosponses to what )
3) Yes some data can be push on the stack before the first function call like you said, i.e argv , parameters...etc (what ever the code segment pushes on the stack first before calling a function will be on the stack first obviously.
4) Yes the stack is just a data segment setup in the GDT/LDT (i.e marked as a data segment in the GDT as opposed to a code segment or gate).
5) I am not sure I fully agree with "That's not true. The stack on x86 always grows down." I thought their was a bit in the gdt segment selector to control this... but if not I thought their is still away to control how the stack grows sinces doesn't BSD os's have a stack that grows the opposite way? But maybe I am wrong and I am getting mixed up with the heap?
6) So their is no way to get the functions names if their is no symbol table if this is true the best somebody can do if the symbol table is stripped is just use a default name like func1, func2 ,...etc (because correct me if I am wrong if their is no symbol table then we only have the return addresses of where the functions existt.) "Their is No other way other then the symbol table that allows you to get the names of functions" ( But either way names are just names not a big issue if you cann't get them, the address's and parameters are the important issues.)
Ok if the above is for the most part correct.
Question 1)
If I set a break point at a particular place in the code segment typically how do the tracer programs get a functions calling stack?
I would think they would analysis the stack not the code segment so this would probably be my reason for the wrong info that I had original posted. Correct me if I am wrong you would analysis the stack not the code segment to get the functions call stack. Basically make a temporary copy of the stack and traverse it backwards in someway recording the function names and address locations/parameters of the functions...
Problem is the "someway" how do you know what is a return address and a functions parameters or other things on the stack. ( when in theory this is just data being pushed on and poped off what differentiates the return address from a parameter)
This would be the only problem in backtracing the stack.
This was stupid of me since I knew most of what I was think about but just ran the other way with it in my mind.
So let me correct myself.
1) cs segment for my program contains the ENTER/LEAVE instructions that are equivalent to push ebp , mov ebp , esp , sub esp , n
It is only the data that the push and pop commands put on the stack obviously not the instructions themself.... unless of course you where pushing shell code on the stack to cause a bufferover flow. So all the instructions are obviously in the code segment and the stack is in the data segment.
( well I will take your word when you say ENTER/LEAVE commands are not common to use in setting up the stack frame thought they where but maybe it was the other 3 statements instead either way you could uses either.... I would pick the fastest instruction )
2) The stack will only contain the data I push onto it from the code segment. But in theory I will have the address of the functions and their parameters from the stack.... at a given instance. (problem is determing what corrosponses to what )
3) Yes some data can be push on the stack before the first function call like you said, i.e argv , parameters...etc (what ever the code segment pushes on the stack first before calling a function will be on the stack first obviously.
4) Yes the stack is just a data segment setup in the GDT/LDT (i.e marked as a data segment in the GDT as opposed to a code segment or gate).
5) I am not sure I fully agree with "That's not true. The stack on x86 always grows down." I thought their was a bit in the gdt segment selector to control this... but if not I thought their is still away to control how the stack grows sinces doesn't BSD os's have a stack that grows the opposite way? But maybe I am wrong and I am getting mixed up with the heap?
6) So their is no way to get the functions names if their is no symbol table if this is true the best somebody can do if the symbol table is stripped is just use a default name like func1, func2 ,...etc (because correct me if I am wrong if their is no symbol table then we only have the return addresses of where the functions existt.) "Their is No other way other then the symbol table that allows you to get the names of functions" ( But either way names are just names not a big issue if you cann't get them, the address's and parameters are the important issues.)
Ok if the above is for the most part correct.
Question 1)
If I set a break point at a particular place in the code segment typically how do the tracer programs get a functions calling stack?
I would think they would analysis the stack not the code segment so this would probably be my reason for the wrong info that I had original posted. Correct me if I am wrong you would analysis the stack not the code segment to get the functions call stack. Basically make a temporary copy of the stack and traverse it backwards in someway recording the function names and address locations/parameters of the functions...
Problem is the "someway" how do you know what is a return address and a functions parameters or other things on the stack. ( when in theory this is just data being pushed on and poped off what differentiates the return address from a parameter)
This would be the only problem in backtracing the stack.
Last edited by Sam111 on Sun Mar 04, 2012 3:02 pm, edited 1 time in total.
Re: writting a stack tracer?
No. As you were told, the 'enter' and 'leave' instructions are rarely used. In fact, after optimisation, it is not always possible to see a consistent function prologue and epilogue at all.1) cs segment for my program contains the ENTER/LEAVE instructions that are equivalent to push ebp , mov ebp , esp , sub esp , n
So if I was writting a stack tracer I would have to go thru the code segment looking for these instructions or calling instructions.
No necessarily. It depends on the processor and the ABI currently in use. Not all compilers, or calling conventions, use the stack to pass parameters and many processors don't have a stack like the one that you currently don't understand.2) The stack will only contain the data I push onto it from the code segment. But in theory I will have the address of the functions and their parameters from the stack.... at a given instance. (problem is determing what corrosponses to what )
Not sure what where you got this info from.5) I am not sure I fully agree with "That's not true. The stack on x86 always grows down." I thought their was a bit in the gdt segment selector to control this... but if not I thought their is still away to control how the stack grows sinces doesn't BSD os's have a stack that grows the opposite way? But maybe I am wrong and I am getting mixed up with the heap?
If there were another way, why would we need to have a symbol table.6) So their is no way to get the functions names if their is no symbol table if this is true the best somebody can do if the symbol table is stripped is just use a default name like func1, func2 ,...etc (because correct me if I am wrong if their is no symbol table then we only have the return addresses of where the functions existt.) "Their is No other way other then the symbol table that allows you to get the names of functions"
You need to do some study.
If a trainstation is where trains stop, what is a workstation ?
Re: writting a stack tracer?
Ok
1) I will take your word on that their is no standard way. Some compilers may use a completely different stack frame or thing in general.
They may keep paramters in registers then call the function instead of pushing them on the stack.
They may just push a reference address on the stack for the parameters .
Their are tons of different ways to call a function or pass a parameters to a function. I know this cdel , stdcalls ,fastcall , and many others you can cook up.
But the main point is when you call the function at least the return address is pushed on the stack so essientially if you can find the last return address then you can find the previous function that called it. And if you can keep recursively doing this you would have the calling stack.
Problem I am wondering about is it possible to always find the previous return address. (I don't think so because at this level their is just data, how else can you differentiate a return address from a parameter on the stack or other things/data on the stack)
If you cann't do that then is their another way or can you not take an arbitary piece of code and find the calling stack?
Question 2
How do debugger / stack trace programs get the calling stack normally.... maybe these are based on not arbitary code their is some predicatable factor placed in the code at compile time that the debugger/stack tracer is specific coded for??? ( and a stack trace can only be done in the enviroment IDE you wrote the code in ) duno.
Question 3)
Now that I think about it do all compilers call a function with the call instruction which at least pushes the return address on the stack or do they every call a function without the call instruction like just a jmp which won't put anything on the stack. Thus screwing up my whole reasoning of 1) that the return address must be at least on the stack?
I would still think at least 99% of the time they use a call statement/ret statement since if not we would have a **** load of spagettie code
Which I guess pasta code isn't bad if you don't have to analysis it and speed is an issue.
1) I will take your word on that their is no standard way. Some compilers may use a completely different stack frame or thing in general.
They may keep paramters in registers then call the function instead of pushing them on the stack.
They may just push a reference address on the stack for the parameters .
Their are tons of different ways to call a function or pass a parameters to a function. I know this cdel , stdcalls ,fastcall , and many others you can cook up.
But the main point is when you call the function at least the return address is pushed on the stack so essientially if you can find the last return address then you can find the previous function that called it. And if you can keep recursively doing this you would have the calling stack.
Problem I am wondering about is it possible to always find the previous return address. (I don't think so because at this level their is just data, how else can you differentiate a return address from a parameter on the stack or other things/data on the stack)
If you cann't do that then is their another way or can you not take an arbitary piece of code and find the calling stack?
Question 2
How do debugger / stack trace programs get the calling stack normally.... maybe these are based on not arbitary code their is some predicatable factor placed in the code at compile time that the debugger/stack tracer is specific coded for??? ( and a stack trace can only be done in the enviroment IDE you wrote the code in ) duno.
Question 3)
Now that I think about it do all compilers call a function with the call instruction which at least pushes the return address on the stack or do they every call a function without the call instruction like just a jmp which won't put anything on the stack. Thus screwing up my whole reasoning of 1) that the return address must be at least on the stack?
I would still think at least 99% of the time they use a call statement/ret statement since if not we would have a **** load of spagettie code
Which I guess pasta code isn't bad if you don't have to analysis it and speed is an issue.
- Brynet-Inc
- Member
- Posts: 2426
- Joined: Tue Oct 17, 2006 9:29 pm
- Libera.chat IRC: brynet
- Location: Canada
- Contact:
Re: writting a stack tracer?
That is architecture dependant, in OpenBSD, HP PA-RISC is the only supported architecture where the stack grows upward.Sam111 wrote:5) I am not sure I fully agree with "That's not true. The stack on x86 always grows down." I thought their was a bit in the gdt segment selector to control this... but if not I thought their is still away to control how the stack grows sinces doesn't BSD os's have a stack that grows the opposite way? But maybe I am wrong and I am getting mixed up with the heap?
Re: writting a stack tracer?
Ok so then the stack in OpenBSD is only growing the other way on other arch's like PA-RISC based machines but if I ran OpenBSD on my intel machine it would grow always down and their would be no way of changing this (because this is fundementally based on the way the push/pop instructions work on that specific arch) correct me if I am wrong?That is architecture dependant, in OpenBSD, HP PA-RISC is the only supported architecture where the stack grows upward.
But you could always not use the push and pop and use mov, add, and sub to make a stack grow differently
( the only thing you would have to watch out for is access/memory violations in doing so)
Simply realize there is no spoon
Last edited by Sam111 on Sun Mar 04, 2012 4:42 pm, edited 1 time in total.
Re: writting a stack tracer?
Ok, if the above 2 of my posts are correct.
Then their is simply no way to get the calling function stack in all cases. It would be dependent on the compiler under development. (i.e weather it put in things on the stack to differentiate between the return address and other data on the stack )
Correct me if I am wrong?
Though I havn't got to the debugging registers yet so maybe those can provide me with the answer I am looking for.
But if these can only be used to step thru the code and not step thru the stack in some way to allow you to differentiate between return address and other data then I will have to say it is not possible in all cases. ( only probablistic )
But I will have to see if the debug registers shed more light on the stack trace issues. Don't want to jump to any conclusions so soon.
Then their is simply no way to get the calling function stack in all cases. It would be dependent on the compiler under development. (i.e weather it put in things on the stack to differentiate between the return address and other data on the stack )
Correct me if I am wrong?
Though I havn't got to the debugging registers yet so maybe those can provide me with the answer I am looking for.
But if these can only be used to step thru the code and not step thru the stack in some way to allow you to differentiate between return address and other data then I will have to say it is not possible in all cases. ( only probablistic )
But I will have to see if the debug registers shed more light on the stack trace issues. Don't want to jump to any conclusions so soon.
- Combuster
- Member
- Posts: 9301
- Joined: Wed Oct 18, 2006 3:45 am
- Libera.chat IRC: [com]buster
- Location: On the balcony, where I can actually keep 1½m distance
- Contact:
Re: writting a stack tracer?
Sam111 wrote:weather
The result is that no compiler will want to talk to your code without breaking.But you could always not use the push and pop and use mov, add, and sub to make a stack grow differently
I also found code on this very website to do stacktraces including descriptions. You'd better go look for it.
Re: writting a stack tracer?
Every good solution is obvious once you've found it.
Re: writting a stack tracer?
your not telling me any thing new that I already don't know.
Basically my question is given an arbitrary programs stack is their away to backtraces it to get the calling function stack / calling functions address's?
in these conditions
1) if you don't know the calling convention ?
2) if you do know the calling convention ?
And I am assuming a function is something that uses at least the call asm instruction so the return address is in theory somewhere on the stack. (but may not use the stack necessary for passing parameters could use registers or other means)
Note: I don't consider storing in registers and jmp to a label to be a function
So this gets rid of my 3 stipulation above. And implies the functions address are at least on the stack...
My thoughts are
1) No because even if you know the calling convention you don't know how much data is pushed on the stack before or after the function address.... since you would have to know how many local variables and function parameters as well to maybe take a crack at that.
2) No because of 1) and the fact you don't even know the calling convention of what goes where on the stack even if you did know how many function parameters their was for a function. (2 is even worse because you wouldn't beable to necessary call a function since you wouldn't even know the calling convention / where the parameters must go for the function call to make any since)
So my final conclusion is a stack track can only be done in debug mode as instructions are executing the debugger program keeps track of the call instructions/function address. But if you didn't do this at the same time you where executing the program then you wouldn't beable to determine this since you would have know way of differentiating data from return function address.
Correct me if I am wrong.
Curious if anybody can find a flaw in my logic or if it is sound.
Basically my question is given an arbitrary programs stack is their away to backtraces it to get the calling function stack / calling functions address's?
in these conditions
1) if you don't know the calling convention ?
2) if you do know the calling convention ?
And I am assuming a function is something that uses at least the call asm instruction so the return address is in theory somewhere on the stack. (but may not use the stack necessary for passing parameters could use registers or other means)
Note: I don't consider storing in registers and jmp to a label to be a function
So this gets rid of my 3 stipulation above. And implies the functions address are at least on the stack...
My thoughts are
1) No because even if you know the calling convention you don't know how much data is pushed on the stack before or after the function address.... since you would have to know how many local variables and function parameters as well to maybe take a crack at that.
2) No because of 1) and the fact you don't even know the calling convention of what goes where on the stack even if you did know how many function parameters their was for a function. (2 is even worse because you wouldn't beable to necessary call a function since you wouldn't even know the calling convention / where the parameters must go for the function call to make any since)
So my final conclusion is a stack track can only be done in debug mode as instructions are executing the debugger program keeps track of the call instructions/function address. But if you didn't do this at the same time you where executing the program then you wouldn't beable to determine this since you would have know way of differentiating data from return function address.
Correct me if I am wrong.
Curious if anybody can find a flaw in my logic or if it is sound.
Last edited by Sam111 on Mon Mar 05, 2012 1:10 pm, edited 1 time in total.
- Combuster
- Member
- Posts: 9301
- Joined: Wed Oct 18, 2006 3:45 am
- Libera.chat IRC: [com]buster
- Location: On the balcony, where I can actually keep 1½m distance
- Contact:
Re: writting a stack tracer?
That's both an epic grammar fail and a truth.Sam111 wrote:your not telling me any thing new that I already don't know.
The question as formulated yields two no's for many reasons.
In practice, an x86 program that's not reusing EBP as a general purpose register and doesn't trash the stack gives an theoretical 100% chance of an accurate stacktrace. Knowing your history, my estimate for you is 50% - go work on that problem.
Re: writting a stack tracer?
your not telling me any thing new that I already don't know.
Basically my question is given an arbitrary programs stack is their away to backtraces it to get the calling function stack / calling functions address's?
in these conditions
1) if you don't know the calling convention ?
2) if you do know the calling convention ?
And I am assuming a function is something that uses at least the call asm instruction so the return address is in theory somewhere on the stack. (but may not use the stack necessary for passing parameters could use registers or other means)
Note: I don't consider storing in registers and jmp to a label to be a function
So this gets rid of my 3 stipulation above. And implies the functions address are at least on the stack...
My thoughts are
1) No because even if you know the calling convention you don't know how much data is pushed on the stack before or after the function address.... since you would have to know how many local variables and function parameters as well to maybe take a crack at that.
2) No because of 1) and the fact you don't even know the calling convention of what goes where on the stack even if you did know how many function parameters their was for a function. (2 is even worse because you wouldn't beable to necessary call a function since you wouldn't even know the calling convention / where the parameters must go for the function call to make any since)
So my final conclusion is a stack track can only be done in debug mode as instructions are executing the debugger program keeps track of the call instructions/function address. But if you didn't do this at the same time you where executing the program then you wouldn't beable to determine this since you would have know way of differentiating data from return function address.
Correct me if I am wrong.
Curious if anybody can find a flaw in my logic or if it is sound.
Ofcourse their is a probablistic way of determining an arbitary stack function calls. By trying to call all possible stack data seeing what fails and what doesn't but even so this is slim to none to work since you need to be sure of the vaild parameters to pass if any when calling. (plus alot more factors though could work in figureing out a small stack completely but I would think it would be very difficult in the general case.
Ofcourse with AI. maybe their is way computers can reason like a human to increase the probablity of being correct.
Also if you where analysising an arbitary stack you are only allowed to look at the stack not the registers because you are just given a copy of the arbitary stack you are not analysising this by running the program or debug mode that allows you to see the registers as well as the stack.... I am talking about just having a stack to work with...
Basically my question is given an arbitrary programs stack is their away to backtraces it to get the calling function stack / calling functions address's?
in these conditions
1) if you don't know the calling convention ?
2) if you do know the calling convention ?
And I am assuming a function is something that uses at least the call asm instruction so the return address is in theory somewhere on the stack. (but may not use the stack necessary for passing parameters could use registers or other means)
Note: I don't consider storing in registers and jmp to a label to be a function
So this gets rid of my 3 stipulation above. And implies the functions address are at least on the stack...
My thoughts are
1) No because even if you know the calling convention you don't know how much data is pushed on the stack before or after the function address.... since you would have to know how many local variables and function parameters as well to maybe take a crack at that.
2) No because of 1) and the fact you don't even know the calling convention of what goes where on the stack even if you did know how many function parameters their was for a function. (2 is even worse because you wouldn't beable to necessary call a function since you wouldn't even know the calling convention / where the parameters must go for the function call to make any since)
So my final conclusion is a stack track can only be done in debug mode as instructions are executing the debugger program keeps track of the call instructions/function address. But if you didn't do this at the same time you where executing the program then you wouldn't beable to determine this since you would have know way of differentiating data from return function address.
Correct me if I am wrong.
Curious if anybody can find a flaw in my logic or if it is sound.
Ofcourse their is a probablistic way of determining an arbitary stack function calls. By trying to call all possible stack data seeing what fails and what doesn't but even so this is slim to none to work since you need to be sure of the vaild parameters to pass if any when calling. (plus alot more factors though could work in figureing out a small stack completely but I would think it would be very difficult in the general case.
Ofcourse with AI. maybe their is way computers can reason like a human to increase the probablity of being correct.
In practice, an x86 program that's not reusing EBP as a general purpose register and doesn't trash the stack gives an theoretical 100% chance of an accurate stacktrace. Knowing your history, my estimate for you is 50% - go work on that problem.
But you can only rely on EBP if the calling convention makes the ebp point to the location of the current stack frame not the later in the quote◦EBP - In functions that store parameters or variables on the stack, the base pointer holds the location of the current stack frame. In other situations, however, EBP is a free data-storage register.
Also if you where analysising an arbitary stack you are only allowed to look at the stack not the registers because you are just given a copy of the arbitary stack you are not analysising this by running the program or debug mode that allows you to see the registers as well as the stack.... I am talking about just having a stack to work with...
Last edited by Sam111 on Mon Mar 05, 2012 1:28 pm, edited 4 times in total.
- Combuster
- Member
- Posts: 9301
- Joined: Wed Oct 18, 2006 3:45 am
- Libera.chat IRC: [com]buster
- Location: On the balcony, where I can actually keep 1½m distance
- Contact:
Re: writting a stack tracer?
Apart from misreferring your own questions and repeating errors after being corrected, you have obviously not realized how stackdumps work in the first place.
Go read the links. All of them. Solar hasn't pointed you at anything new, yet you didn't know it. (And you are consistently failing at lying )
Go read the links. All of them. Solar hasn't pointed you at anything new, yet you didn't know it. (And you are consistently failing at lying )