source: trunk/website/scripts/blackbox/upload.php @ 2690

Revision 2690, 5.9 KB checked in by lowjoel, 3 years ago (diff)

Factorise the QueryStatus? function to allow us to determine the most likely report matching a given set of exceptions and its stack traces. The output is backwards-compatible with the old 6.1 clients since no XML attributes are removed, only an extra id attribute is returned, indicating the most likely report ID.

Line 
1<?php
2if (empty($_POST) || empty($_POST['action']))
3    exit;
4
5ob_start();
6require('../Database.php');
7
8function GetFunctionNameFromStackTrace($line)
9{
10    $result = GetStackFrameInformation($line);
11    return $result->function;
12}
13
14function GetStackFrameInformation($line)
15{
16    //at Eraser.Program.OnGUIInitInstance(Object sender) in D:\Development\Projects\Eraser 6.2\Eraser\Program.cs:line 191
17    $matches = array();
18    $function = $file = $line = null;
19    if (preg_match('/^([^   ]+) (.*) ([^    ]+) (.*):([^    ]+) ([0-9]+)/', $line, $matches))
20    {
21        $function = $matches[2];
22        $file = $matches[4];
23        $line = intval($matches[6]);
24    }
25    else if (preg_match('/^([^  ]+) (.*)/', $line, $matches))
26    {
27        $function = $matches[2];
28    }
29
30    return (object)array('function' => $function, 'file' => $file, 'line' => $line);
31}
32
33function GetExceptionIDFromExceptionInfo($exception, $exceptionDepth)
34{
35    $pdo = new Database();
36    $stackFrames = '';
37    foreach ($exception as $stackIndex => $stackFrame)
38    {
39        //Ignore the exception key; that stores the exception type.
40        if ($stackIndex == 'exception')
41            continue;
42
43        $stackFrames .= sprintf('(StackFrameIndex=%d AND Function=%s) OR ',
44            $stackIndex, $pdo->quote(GetFunctionNameFromStackTrace($stackFrame)));
45    }
46
47    if (empty($stackFrames))
48        return null;
49
50    //Query for the list of exceptions containing the given functions
51    $statement = $pdo->prepare(sprintf('SELECT DISTINCT(blackbox_exceptions.ID) AS ExceptionID FROM blackbox_stackframes
52        INNER JOIN blackbox_exceptions ON blackbox_stackframes.ExceptionID=blackbox_exceptions.ID
53        WHERE (%s) AND ExceptionDepth=? AND ExceptionType=?',
54        substr($stackFrames, 0, strlen($stackFrames) - 4)));
55    $statement->bindParam(1, $exceptionDepth);
56    $statement->bindParam(2, $exception['exception']);
57    $statement->execute();
58
59    if ($statement->rowCount() == 0)
60        return false;
61    $row = $statement->fetch();
62    return $row['ExceptionID'];
63}
64
65function QueryStatus($stackTrace)
66{
67    $status = 'exists';
68    $reportID = false;
69    $exceptionIDs = array();
70    $pdo = new Database();
71   
72    foreach ($stackTrace as $exceptionDepth => $exception)
73    {
74        $exceptionID = GetExceptionIDFromExceptionInfo($exception, $exceptionDepth);
75        if ($exceptionID === null)
76            continue;
77        else if ($exceptionID === false)
78        {
79            $status = 'new';
80            break;
81        }
82        else
83            //Store the current exception ID on the stack
84            array_push($exceptionIDs, $exceptionID);
85    }
86
87    header('Content-Type: application/xml');
88   
89    //If this is an existing exception, try to find the most similar report.
90    if ($status == 'exists' && count($exceptionIDs) > 0)
91    {
92        $ids = implode(', ', $exceptionIDs);
93        $result = $pdo->query(sprintf('SELECT ReportID, COUNT(ID) as Matches FROM blackbox_exceptions
94            WHERE ID IN (%s) GROUP BY ReportID ORDER BY Matches DESC', $ids));
95        if ($result->rowCount() > 0)
96        {
97            $row = $result->fetch();
98            $reportID = $row['ReportID'];
99
100            printf('<?xml version="1.0"?>
101<crashReport status="exists" id="%s" />', htmlspecialchars($status), $reportID);
102            return;
103        }
104    }
105
106    //Otherwise just return the status of the report.
107    printf('<?xml version="1.0"?>
108<crashReport status="%s" />', htmlspecialchars($status));
109}
110
111function Upload($stackTrace, $crashReport)
112{
113    $pdo = new Database();
114    $pdo->beginTransaction();
115
116    $statement = $pdo->prepare('INSERT INTO blackbox_reports SET IPAddress=?');
117    $statement->bindParam(1, sprintf('%u', ip2long($_SERVER['REMOTE_ADDR'])));
118    try
119    {
120        $statement->execute();
121    }
122    catch (PDOException $e)
123    {
124        throw new Exception('Could not insert crash report into Reports table', null, $e);
125    }
126
127    $reportId = $pdo->lastInsertId();
128    $exceptionInsert = $pdo->prepare('INSERT INTO blackbox_exceptions
129        SET ReportID=?, ExceptionType=?, ExceptionDepth=?');
130    $exceptionInsert->bindParam(1, $reportId);
131    $stackFrameInsert = $pdo->prepare('INSERT INTO blackbox_stackframes SET
132        ExceptionID=?, StackFrameIndex=?, Function=?, File=?, Line=?');
133    foreach ($stackTrace as $exceptionDepth => $exception)
134    {
135        $exceptionInsert->bindParam(2, $exception['exception']);
136        $exceptionInsert->bindParam(3, $exceptionDepth);
137        try
138        {
139            $exceptionInsert->execute();
140        }
141        catch (PDOException $e)
142        {
143            throw new Exception('Could not insert exception into Exceptions table', null, $e);
144        }
145       
146        $exceptionId = $pdo->lastInsertId();
147        foreach ($exception as $stackIndex => $stackFrame)
148        {
149            //Ignore the exception key; that stores the exception type.
150            if ((string)$stackIndex == 'exception')
151                continue;
152           
153            $stackFrameInfo = GetStackFrameInformation($stackFrame);
154
155            $stackFrameInsert->bindParam(1, $exceptionId);
156            $stackFrameInsert->bindParam(2, $stackIndex);
157            $stackFrameInsert->bindParam(3, $stackFrameInfo->function);
158            $stackFrameInsert->bindParam(4, $stackFrameInfo->file);
159            $stackFrameInsert->bindParam(5, $stackFrameInfo->line);
160            try
161            {
162                $stackFrameInsert->execute();
163            }
164            catch (PDOException $e)
165            {
166                throw new Exception('Could not insert stack frame into Stack Frames table', null, $e);
167            }
168        }
169    }
170   
171    $pdo->commit();
172
173    //Move the temporary file to out dumps folder for later inspection
174    $localName = $crashReport['name'];
175    $lastDot = strrpos($localName, '.');
176    if ($lastDot !== false)
177        $localExt = substr($localName, strrpos($localName, '.') + 1);
178    else
179        $localExt = 'bz2';
180    if (!move_uploaded_file($crashReport['tmp_name'], 'dumps/' . $reportId . '.tar.' . $localExt))
181        throw new Exception('Could not store crash dump onto server.');
182}
183
184try
185{
186    switch ($_POST['action'])
187    {
188        case 'status':
189            if (empty($_POST['stackTrace']))
190                exit;
191            QueryStatus($_POST['stackTrace']);
192            break;
193   
194        case 'upload':
195            if (empty($_FILES) || empty($_POST['stackTrace']))
196                exit;
197            Upload($_POST['stackTrace'], $_FILES['crashReport']);
198            break;
199    }
200}
201catch (Exception $e)
202{
203    ob_end_clean();
204    header('HTTP/1.1 500 Internal Server Error');
205    printf('<?xml version="1.0"?>
206<error>%s</error>', htmlspecialchars($e->getMessage()));
207}
208?>
Note: See TracBrowser for help on using the repository browser.