0

I've came up with the most convoluted example but still can't find the purpose of the catchHigh field. I find it that the right handler can be selected simply by looking at the tryblock map from start to end and comparing the ranges of the tryLow and tryHigh fields to the current state.

I mean I understand that catchHigh is simply the max state that the catch handlers can reach (inclusive?) (from here ehdata.h:140:

__ehstate_t     catchHigh;          // Highest state index of any associated catch

) but I don't understand when this field can be actually useful.

However it seems that this value is used by the CRT anyway (vs2019 - frame.cpp:693):

/////////////////////////////////////////////////////////////////////////////
//
// GetRangeOfTrysToCheck - determine which try blocks are of interest, given
//   the current catch block nesting depth.  We only check the trys at a single
//   depth.
//
// Returns:
//      Address of first try block of interest is returned
//      pStart and pEnd get the indices of the range in question
//
RENAME_EH_EXTERN(__FrameHandler3)::TryBlockMap::IteratorPair RENAME_EH_EXTERN(__FrameHandler3)::GetRangeOfTrysToCheck(
    RENAME_EH_EXTERN(__FrameHandler3)::TryBlockMap &TryBlockMap,
    __ehstate_t                                    curState,
    DispatcherContext                              * /*pDC*/,
    FuncInfo                                       * pFuncInfo,
    int                                            CatchDepth
) {
    TryBlockMapEntry *pEntry = FUNC_PTRYBLOCK(*pFuncInfo, 0);
    unsigned start = FUNC_NTRYBLOCKS(*pFuncInfo);
    unsigned end = start;
    unsigned end1 = end;

    while (CatchDepth >= 0) {
            _VCRT_VERIFY(start != -1);
            start--;
            if ( TBME_HIGH(pEntry[start]) < curState && curState <= TBME_CATCHHIGH(pEntry[start])
                    || (start == -1)
            ) {
                    CatchDepth--;
                    end = end1;
                    end1 = start;
                    }
            }

    ++start; // We always overshoot by 1 (we may even wrap around)
    _VCRT_VERIFY(end <= FUNC_NTRYBLOCKS(*pFuncInfo) && start <= end);
    auto iterStart = TryBlockMap::iterator(TryBlockMap, start);
    auto iterEnd = TryBlockMap::iterator(TryBlockMap, end);

    return TryBlockMap::IteratorPair(iterStart, iterEnd);
}

Btw TBME_HIGH is tryHigh field and TBME_CATCHHIGH is the topic - catchHigh.

However I can't seem to follow the above code - what I can do however is show you my example:

#include <cstdio>
#include <Windows.h>
#include <winnt.h>
#include <malloc.h>
extern "C" void* _ReturnAddress(void);
void f() {
    if (!printf(""))
        throw 9;
}

struct test {
    test();
    test(const test&);
    ~test();
    test f1(int, test);
    int a, b;
} f(int, test) throw(int) {
    test tmp;
    f();
    return tmp;
}

int printfproxy(size_t szbuffer, const char* format, ...)
{
    va_list lst;
    va_start(lst, format);
    char* tmp = (char*)_alloca(szbuffer);
    int a = vsprintf(tmp, format, lst);
    OutputDebugStringA(tmp);
    return a;
}

test test::f1(int, test a) throw(int) {
    test tmp;
    return tmp;
}

test::test() : a((int)_ReturnAddress()), b(0) {
    printfproxy(sizeof "ctor test @ ffffffff\n", "ctor test @ %X\n", a);
}


test::test(const test& arg) : a((int)_ReturnAddress()), b(arg.a)
{
    printfproxy(sizeof "copy ctor test @ ffffffff\n", "copy ctor test @ %X\n", a);
}

test::~test() {
    printfproxy(sizeof "de ctor test @ ffffffff\n", "de ctor test @ %X\n", a);
}

struct test4 {
    int a[3];
} f4(int, struct test4 arg) throw(int) {
    test4 tmp;
    return tmp;
}

void main() {
    test tmp;
    test4 tmp2;
    try {
        test tmp, tmp3;
        test4 tmp2;
        try {
            if (tmp.b == 0)
#line 93
                f(9, tmp).f1(9, tmp), f(7, tmp);
            else
                f(7, tmp), f(7, tmp), f4(7, tmp2);
        }
        catch (int) {
            try {
#line 99
                printfproxy(sizeof "handler 2147483647""\n", "handler %d""\n", __LINE__); test tmphandler; f(7, tmp);
            }
            catch (int) {
#line 102
                printfproxy(sizeof "handler 2147483647""\n", "handler %d""\n", __LINE__);
            }
        }
#line 106
        if (tmp.b == 0)
            f(9, tmp).f1(9, tmp), f(7, tmp);
        else
            f(7, tmp), f(7, tmp), f4(7, tmp2);
    }
#line 111
    catch (int) { printfproxy(sizeof "handler 2147483647""\n", "handler %d""\n", __LINE__); }
}

Here is the tryblockmap generated by the compiler:

stru_40C9F0     TryBlockMapEntry <11, 12, 13, 1, offset stru_40C9C0> ; offset loc_401554
                TryBlockMapEntry <4, 9, 13, 1, offset stru_40C9D0> ; offset loc_4014FF
                TryBlockMapEntry <1, 18, 19, 1, offset stru_40C9E0> ; offset loc_4015FD

And here is the point of first exception inside main function (at line 93):

graph1

We see that at this point the state is 5 (state is represented by var_4 in ida).

Mind the break-point - the actual exception happens at the call to f - few instructions later.

So we look at the smallest range that fits - that is the second trymapentry (offset loc_4014FF). So we execute the handler at line 99:

graph

Now there in the initial instructions, more specifically the call to printfproxy the state is still - the tryHigh of the previous step + 1 so 10. Then when we enter the call to f the state becomes 0xC (12). Now at this point when the exception occurs inside f the first trymapentry (offset loc_401554) is executed (line 102) for the same reason as before - it's the smallest range that fits the current state (note that the range is inclusive since:

typedef const struct _s_TryBlockMapEntry {
    __ehstate_t     tryLow;             // Lowest state index of try
    __ehstate_t     tryHigh;            // Highest state index of try

and so the 12 fits here at the end).

Also funny thing to notice is that the appropriate trymapentry starts at state 11 and so if the exception occurred in the beginning where we established that the state is 10 the outermost handler will be executed (line 111) - however that is impossible since the compiler has determined that printfproxy can't throw.

Anyway now let's execute another handler further:

graph3

Here we continue execution at line 106 where another exception is about to occur. This time the state is set to 0xE (14). Which means it's time to execute the outermost handler (offset loc_4015FD @line 111) because it's the only range that fits.

My point is we never once reached a point where catchHigh was needed. We only used the idea of the smallest possible range that fits current state. My question is - could there be a situation where this field would be actually needed to determine the right handler?

Note smallest possible range in my mind is if the range is contained in another range - that range. If I had to write an algorithm to find the right handler given state and tryblockmap I would do something like (pseudo-code using range for loop) and never use catchHigh:

findhandler(
    state,
    tryblockmap
) {
    int loweststate = INT_MIN, maxstate = INT_MAX;
    auto handler = nullptr;
    for (auto a : tryblockmap)
        if (a.tryLow <= state && a.tryHigh >= state) //if it fits
            if (a.tryLow > loweststate || a.tryHigh < maxstate) //if it smaller range then previous handler
                handler = a, loweststate = a.tryLow, maxstate = a.tryHigh;

    return handler;
}

Note - I'm using VC++6.0 SP5 PP5 to compile.

2
  • Maybe try an example with several catches for the same try
    – Igor Skochinsky
    May 5 '20 at 6:38
  • @IgorSkochinsky I tried and the ranges increases after each catch so still no purpose of the catchHIgh field although i understand now how it's used - it's just checked if a state is in the range of any of it's catch handlers and if it's so the handler owning those catches is excluded from the search.
    – rec
    May 5 '20 at 12:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.