Well, it all starts by understanding.
What is the code supposed to do.
What were the documented requirements (hah, that was a joke, you probably don't have any...)
How is the code supposed to do it.
You need to gain an overall understanding of how the modules fit together (hopefully with 1000's of lines of code, you have more than one procedure/package/function)
Once you understand what the code is supposed to do and conceptually how it is supposed to do that - you need to identify "where it is going wrong"
This is why I like to have heavily instrumented code.
http://www.google.com/search?q=site%3Atkyte.blogspot.com+instrumentation that way, you can debug in production without a debugger - you can debug from anywhere (it is after all how Oracle does it - got a bug, turn on tracing, turn on an event, get the trace file - Oracle is HEAVILY instrumented)
You do have a debugger - sqldeveloper has a nice one - but unless you understand what the code is SUPPOSED to do and conceptually HOW it does it - using a debugger on someone else's code is virtually useless. (if you don't know how the code is supposed to work, how will you know when something goes wrong).
So, I rely on heavily instrumented code (if I inherited a bunch of un-instrumented code, that would be my first plan - to instrument - at all costs - first and foremost (after understanding of course)).
As for performance, that is algorithm tuning. For procedural code, that means you have some experience with lots of ways of doing "things". You know there are 100 ways to do anything and sometimes one way is better than the other depending on the circumstance (this is why we have clustered b*tree indexes, b*tree indexes, bitmap indexes, text indexes, spatial indexes, etc ... because one index is not sufficient for all problems). Tools you have at your disposal for that would be SQL_TRACE, TKPROF, DBMS_PROFILER and sqldeveloper (again...)